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,4739 @@
1
+ """Concrete execution bridge for prepared portfolio-evolution campaigns.
2
+
3
+ The preparation API deliberately knows nothing about mutable optimizer state.
4
+ This adapter supplies that missing runtime: it evaluates the prepared seeds,
5
+ maintains one benchmark-bound Pareto archive, binds each parent through the
6
+ prepared :class:`CampaignWorkloadPorts`, executes real
7
+ :class:`PortfolioEvolution` and :class:`PortfolioRecombination` waves, and
8
+ projects their evidence into :class:`EvolutionCampaignScheduler` receipts.
9
+
10
+ Injected ports keep each experimental choice replaceable: parent selection,
11
+ portfolio-wave construction, parent-local context retrieval, post-generation
12
+ outcome updates, reflection execution, closed-loop learning, recombination
13
+ utility, prompt rendering, and owned-resource cleanup. The runtime owns only
14
+ ordering, validation, and evidence publication across those boundaries.
15
+
16
+ No workload, objective name, model, or provider is named in this module.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import hashlib
22
+ import json
23
+ import math
24
+ import re
25
+ from dataclasses import dataclass, field, replace
26
+ from enum import Enum
27
+ from typing import Protocol, runtime_checkable
28
+
29
+ from agent_evolve.agentic import PhenotypeIdentity, PortfolioEvolutionComposition
30
+ from agent_evolve.application.agentic_evolution import EvolutionCandidate
31
+ from agent_evolve.application.budgeted_optimizer import (
32
+ OptimizerState,
33
+ pareto_archive_snapshot_hash,
34
+ )
35
+ from agent_evolve.application.campaign_execution import (
36
+ CampaignArchiveCutoffReceipt,
37
+ CampaignArchiveCutoffRequest,
38
+ CampaignCleanupReceipt,
39
+ CampaignCleanupRequest,
40
+ CampaignExecutionStatus,
41
+ CampaignExecutionStartReceipt,
42
+ CampaignFinalizationReceipt,
43
+ CampaignFinalizationRequest,
44
+ CampaignReflectionReceipt,
45
+ CampaignReflectionRequest,
46
+ CampaignReflectionStatus,
47
+ CampaignReflectionTestAdmissionReceipt,
48
+ CampaignReflectionTestAdmissionRequest,
49
+ CampaignSeedExecutionReceipt,
50
+ CampaignSelectorAuditReceipt,
51
+ CampaignStageReceipt,
52
+ CampaignStageRequest,
53
+ SelectorAuditExecutionMode,
54
+ encode_selector_audit_text,
55
+ )
56
+ from agent_evolve.application.campaign_capacity_recourse import (
57
+ CampaignCapacityRecoursePort,
58
+ CampaignCapacityRecourseRequest,
59
+ validate_campaign_capacity_recourse_port,
60
+ validate_campaign_capacity_recourse_result,
61
+ )
62
+ from agent_evolve.application.concurrent_stage import gather_concurrent_stage
63
+ from agent_evolve.application.contextual_campaign_planning import (
64
+ CampaignContextualSearchPlanner,
65
+ )
66
+ from agent_evolve.application.contextual_delayed_credit import (
67
+ observe_contextual_post_recombination_credit,
68
+ observe_contextual_post_stage_survival,
69
+ observe_contextual_terminal_persistence,
70
+ )
71
+ from agent_evolve.application.evolution_campaign import (
72
+ ArchiveUtilitySnapshot,
73
+ CampaignGenerationKind,
74
+ CampaignReflectionWave,
75
+ CampaignWorkloadPorts,
76
+ ParentVariationBinding,
77
+ PreparedEvolutionCampaign,
78
+ ReflectionVisibility,
79
+ )
80
+ from agent_evolve.application.campaign_evidence_registry import (
81
+ CampaignEvidenceRegistry,
82
+ )
83
+ from agent_evolve.application.campaign_selector_context_extension import (
84
+ CAMPAIGN_SELECTOR_CONTEXT_EXTENSION_KEY,
85
+ resolve_campaign_selector_context_extension,
86
+ )
87
+ from agent_evolve.application.campaign_search_phase import (
88
+ attach_campaign_search_phase_context,
89
+ campaign_search_phase_context,
90
+ )
91
+ from agent_evolve.application.campaign_variation_envelope import (
92
+ CampaignVariationEnvelopeLane,
93
+ CampaignVariationEnvelopePolicy,
94
+ CampaignVariationEnvelopeRequest,
95
+ campaign_variation_envelope_context_record,
96
+ campaign_variation_envelope_trace_record,
97
+ validate_campaign_variation_envelope_result,
98
+ )
99
+ from agent_evolve.application.identifiable_reflection_evidence import (
100
+ IdentifiableReflectionEvidenceSnapshot,
101
+ NoIdentifiableMutationEvidenceError,
102
+ ReflectionFalsificationFeedback,
103
+ project_identifiable_reflection_evidence,
104
+ )
105
+ from agent_evolve.application.pareto_archive import ParetoArchive, ParetoDecision
106
+ from agent_evolve.application.parent_measurement import (
107
+ attach_parent_measurement_to_context,
108
+ bind_parent_measurement,
109
+ )
110
+ from agent_evolve.application.portfolio_evolution import (
111
+ MEMORY_ESTIMAND_CONTEXT_KEY,
112
+ PortfolioMemoryCreditBatchPreparation,
113
+ PortfolioMemoryContextProjectionBinding,
114
+ PortfolioVariationWaveRequest,
115
+ PortfolioVariationWaveResult,
116
+ PortfolioMemberDisposition,
117
+ )
118
+ from agent_evolve.application.portfolio_recombination import (
119
+ PortfolioRecombination,
120
+ PortfolioRecombinationWaveRequest,
121
+ PortfolioRecombinationWaveResult,
122
+ )
123
+ from agent_evolve.campaign_workload import AgenticCampaignWorkloadConfig
124
+ from agent_evolve.domain.patch import require_sha256
125
+ from agent_evolve.domain.typed_json import (
126
+ FrozenJsonObject,
127
+ freeze_json,
128
+ thaw_json,
129
+ typed_json_sha256,
130
+ )
131
+ from agent_evolve.policies.variation.typed_patch import derive_patch
132
+ from agent_evolve.policies.memory.global_falsification import (
133
+ AuthenticatedHypothesisObservation,
134
+ GlobalEvidenceRegistrySnapshot,
135
+ )
136
+ from agent_evolve.integrations.pydantic_ai.portfolio_selection import (
137
+ render_portfolio_selection_prompt,
138
+ )
139
+ from agent_evolve.policies.selection.archive_elite import (
140
+ TaskKeyedArchiveEliteParentPolicy,
141
+ TaskKeyedArchiveReservoirParentPolicy,
142
+ )
143
+ from agent_evolve.policies.selection.elite_explorer import (
144
+ TaskKeyedArchiveEliteExplorerParentPolicy,
145
+ )
146
+ from agent_evolve.policies.selection.frozen_archive_pairs import (
147
+ FrozenArchiveSourceUtilityReceipt,
148
+ )
149
+ from agent_evolve.policies.selection.residual_frontier import (
150
+ RESIDUAL_FRONTIER_POLICY_DEFINITION_SHA256,
151
+ residual_anchor_parents,
152
+ residual_frontier_geometry,
153
+ )
154
+ from agent_evolve.ports.portfolio_selection import PortfolioSelectionRequest
155
+ from agent_evolve.ports.contextual_search_allocation import (
156
+ ContextualPortfolioAllocationContract,
157
+ )
158
+ from agent_evolve.ports.frontier_target import (
159
+ CampaignPortfolioFrontierTarget,
160
+ CampaignPortfolioFrontierTargetAllocator,
161
+ )
162
+ from agent_evolve.ports.objective_resolution import (
163
+ EXACT_OBJECTIVE_RESOLUTION_DEFINITION_SHA256,
164
+ EXACT_OBJECTIVE_RESOLUTION_POLICY_ID,
165
+ EXACT_OBJECTIVE_RESOLUTION_POLICY_VERSION,
166
+ objective_resolution_policy_metadata,
167
+ )
168
+ from agent_evolve.ports.parent_measurement import (
169
+ ParentMeasurementBinding,
170
+ ParentMeasurementProjection,
171
+ )
172
+ from agent_evolve.ports.archive_context import (
173
+ CampaignPortfolioArchiveContextProjection,
174
+ CampaignPortfolioArchiveContextProjector,
175
+ )
176
+
177
+
178
+ def _object(value: dict[str, object]) -> FrozenJsonObject:
179
+ frozen = freeze_json(value)
180
+ if type(frozen) is not FrozenJsonObject: # pragma: no cover - closed input.
181
+ raise AssertionError("runtime record did not freeze to an object")
182
+ return frozen
183
+
184
+
185
+ def _canonical_text(value: object) -> str:
186
+ return json.dumps(
187
+ value,
188
+ allow_nan=False,
189
+ ensure_ascii=True,
190
+ separators=(",", ":"),
191
+ sort_keys=True,
192
+ )
193
+
194
+
195
+ _CAMPAIGN_ROLE_TOKEN = re.compile(r"^[a-z][a-z0-9_.-]{0,95}$")
196
+ CAMPAIGN_CONTEXTUAL_HISTORY_KEY = "campaign_contextual_history"
197
+ CAMPAIGN_ARCHIVE_CONTEXT_KEY = "campaign_archive_context"
198
+ CAMPAIGN_FRONTIER_TARGET_KEY = "campaign_frontier_target"
199
+ CAMPAIGN_VARIATION_ENVELOPE_KEY = "campaign_variation_envelope"
200
+ CAMPAIGN_IDENTIFIABLE_REFLECTION_BINDING_KEY = (
201
+ "campaign_identifiable_reflection_binding"
202
+ )
203
+ MEMORY_ESTIMAND_STRATUM_SHA256_KEY = "memory_estimand_stratum_sha256"
204
+ _LOWER_SHA256 = frozenset("0123456789abcdef")
205
+ _OUTCOME_PREPARATION_DOMAIN = (
206
+ b"agent-evolve:campaign-portfolio-outcome-preparation:v1\x00"
207
+ )
208
+ _LEARNING_PREPARATION_DOMAIN = (
209
+ b"agent-evolve:campaign-portfolio-learning-preparation:v1\x00"
210
+ )
211
+ _WAVE_PREPARATION_DOMAIN = b"agent-evolve:campaign-portfolio-wave-preparation:v1\x00"
212
+ _IDENTIFIABLE_REFLECTION_QUERY_DOMAIN = (
213
+ b"agent-evolve:campaign-identifiable-reflection-query:v1\x00"
214
+ )
215
+
216
+
217
+ class RecombinationEvaluationAllocationMode(str, Enum):
218
+ """Choose which generic proposal port receives recombination capacity.
219
+
220
+ ``NATIVE_THEN_RECOURSE`` preserves the production behavior: evaluate the
221
+ replay-safe native recombination slate and ask the injected recourse port
222
+ only to fill unused occurrences. ``RECOURSE_ONLY`` is an explicit
223
+ prospective ablation that gives the complete stage budget to the recourse
224
+ specialist. Neither mode names a workload, objective, provider, or
225
+ concrete proposal algorithm.
226
+ """
227
+
228
+ NATIVE_THEN_RECOURSE = "native_then_recourse"
229
+ RECOURSE_ONLY = "recourse_only"
230
+ _IDENTIFIABLE_REFLECTION_SOURCE_DOMAIN = (
231
+ b"agent-evolve:campaign-identifiable-reflection-source:v1\x00"
232
+ )
233
+ _IDENTIFIABLE_REFLECTION_INPUT_DOMAIN = (
234
+ b"agent-evolve:campaign-identifiable-reflection-input:v1\x00"
235
+ )
236
+
237
+
238
+ def _require_role_token(value: str, name: str) -> None:
239
+ if type(value) is not str or _CAMPAIGN_ROLE_TOKEN.fullmatch(value) is None:
240
+ raise ValueError(f"{name} must use the closed lowercase token grammar")
241
+
242
+
243
+ def _require_sha256(value: str, name: str) -> None:
244
+ if (
245
+ type(value) is not str
246
+ or len(value) != 64
247
+ or any(character not in _LOWER_SHA256 for character in value)
248
+ ):
249
+ raise ValueError(f"{name} must be a lowercase SHA-256 digest")
250
+
251
+
252
+ def _preparation_sha256(domain: bytes, record: FrozenJsonObject) -> str:
253
+ return hashlib.sha256(
254
+ domain + _canonical_text(thaw_json(record)).encode("ascii", errors="strict")
255
+ ).hexdigest()
256
+
257
+
258
+ def _candidate_record(candidate: EvolutionCandidate) -> dict[str, object]:
259
+ EvolutionCandidate.__post_init__(candidate)
260
+ record: dict[str, object] = {
261
+ "candidate_id": candidate.candidate_id.value,
262
+ "configuration_sha256": candidate.occurrence.configuration_hash,
263
+ "generation": candidate.generation,
264
+ "label": candidate.label,
265
+ "valid": candidate.valid,
266
+ "operator_compliant": candidate.operator_compliant,
267
+ "evidence_compliant": candidate.evidence_compliant,
268
+ "objectives": [
269
+ {"metric_id": name, "value_hex": value.hex()}
270
+ for name, value in candidate.objectives
271
+ ],
272
+ "operator_kind": (
273
+ None if candidate.operator_kind is None else candidate.operator_kind.value
274
+ ),
275
+ "parent_ids": [value.value for value in candidate.parent_ids],
276
+ "common_ancestor_id": (
277
+ None
278
+ if candidate.common_ancestor_id is None
279
+ else candidate.common_ancestor_id.value
280
+ ),
281
+ }
282
+ if candidate.objective_resolution_receipt is not None:
283
+ record["objective_resolution"] = (
284
+ candidate.objective_resolution_receipt.to_record()
285
+ )
286
+ return record
287
+
288
+
289
+ def _archive_decision_record(decision: ParetoDecision) -> dict[str, object]:
290
+ """Project archive evidence without copying evaluator free text."""
291
+
292
+ if type(decision) is not ParetoDecision:
293
+ raise TypeError("decision must be an exact ParetoDecision")
294
+ ParetoDecision.__post_init__(decision)
295
+ record = decision.to_trace_record()
296
+ record["failure_details"] = [
297
+ {
298
+ "reason": reason.value,
299
+ "detail_sha256": hashlib.sha256(
300
+ detail.encode("utf-8", errors="strict")
301
+ ).hexdigest(),
302
+ }
303
+ for reason, detail in decision.failure_details
304
+ ]
305
+ record["failure_detail_projection"] = "content_minimized_sha256"
306
+ return record
307
+
308
+
309
+ def _cache_misses(snapshot: dict[str, int | None]) -> int:
310
+ value = snapshot.get("misses")
311
+ if type(value) is not int or value < 0:
312
+ raise RuntimeError("engine evaluation cache omitted exact miss accounting")
313
+ return value
314
+
315
+
316
+ @dataclass(frozen=True, slots=True)
317
+ class CampaignParentLane:
318
+ """One explicitly named parent role selected for a portfolio stage."""
319
+
320
+ lane_id: str
321
+ parent: EvolutionCandidate
322
+
323
+ def __post_init__(self) -> None:
324
+ _require_role_token(self.lane_id, "lane_id")
325
+ if type(self.parent) is not EvolutionCandidate:
326
+ raise TypeError("parent must be an exact EvolutionCandidate")
327
+ EvolutionCandidate.__post_init__(self.parent)
328
+
329
+ def to_record(self) -> dict[str, object]:
330
+ self.__post_init__()
331
+ return {
332
+ "lane_id": self.lane_id,
333
+ "parent_candidate_id": self.parent.candidate_id.value,
334
+ "parent_configuration_sha256": (self.parent.occurrence.configuration_hash),
335
+ }
336
+
337
+
338
+ @dataclass(frozen=True, slots=True)
339
+ class CampaignDecisionSlot:
340
+ """One named campaign decision bound to exactly one parent lane.
341
+
342
+ The current campaign protocol admits one primary slot per lane. Making the
343
+ identity explicit now prevents workload adapters from inferring scientific
344
+ roles from tuple position and leaves a typed extension point for matched
345
+ treatment/control slots later.
346
+ """
347
+
348
+ slot_id: str
349
+ lane_id: str
350
+ role_id: str = "primary"
351
+
352
+ def __post_init__(self) -> None:
353
+ _require_role_token(self.slot_id, "slot_id")
354
+ _require_role_token(self.lane_id, "lane_id")
355
+ _require_role_token(self.role_id, "role_id")
356
+
357
+ def to_record(self) -> dict[str, object]:
358
+ self.__post_init__()
359
+ return {
360
+ "slot_id": self.slot_id,
361
+ "lane_id": self.lane_id,
362
+ "role_id": self.role_id,
363
+ }
364
+
365
+
366
+ @dataclass(frozen=True, slots=True)
367
+ class CampaignParentSelection:
368
+ """Parents selected from one exact optimizer state plus policy evidence."""
369
+
370
+ parents: tuple[EvolutionCandidate, ...]
371
+ evidence: FrozenJsonObject
372
+ lanes: tuple[CampaignParentLane, ...] = ()
373
+ decision_slots: tuple[CampaignDecisionSlot, ...] = ()
374
+
375
+ def __post_init__(self) -> None:
376
+ if type(self.parents) is not tuple or not self.parents:
377
+ raise ValueError("campaign parent selection requires parents")
378
+ if any(type(value) is not EvolutionCandidate for value in self.parents):
379
+ raise TypeError("parents must contain exact EvolutionCandidate values")
380
+ for value in self.parents:
381
+ EvolutionCandidate.__post_init__(value)
382
+ if len({value.candidate_id for value in self.parents}) != len(self.parents):
383
+ raise ValueError("campaign parents must be distinct occurrences")
384
+ if type(self.evidence) is not FrozenJsonObject:
385
+ raise TypeError("parent-selection evidence must be frozen typed JSON")
386
+ if freeze_json(self.evidence) is not self.evidence:
387
+ raise TypeError("parent-selection evidence must already be frozen")
388
+ lanes = self.lanes
389
+ if not lanes:
390
+ lanes = tuple(
391
+ CampaignParentLane(
392
+ lane_id=f"parent_{index + 1:04d}",
393
+ parent=parent,
394
+ )
395
+ for index, parent in enumerate(self.parents)
396
+ )
397
+ object.__setattr__(self, "lanes", lanes)
398
+ if type(lanes) is not tuple or any(
399
+ type(value) is not CampaignParentLane for value in lanes
400
+ ):
401
+ raise TypeError("lanes must contain exact CampaignParentLane values")
402
+ for lane in lanes:
403
+ CampaignParentLane.__post_init__(lane)
404
+ if len(lanes) != len(self.parents) or any(
405
+ lane.parent is not parent
406
+ for lane, parent in zip(lanes, self.parents, strict=True)
407
+ ):
408
+ raise ValueError("parent lanes must align exactly with selected parents")
409
+ lane_ids = tuple(lane.lane_id for lane in lanes)
410
+ if len(set(lane_ids)) != len(lane_ids):
411
+ raise ValueError("parent lane IDs must be unique")
412
+
413
+ slots = self.decision_slots
414
+ if not slots:
415
+ slots = tuple(
416
+ CampaignDecisionSlot(
417
+ slot_id=f"{lane.lane_id}.primary",
418
+ lane_id=lane.lane_id,
419
+ )
420
+ for lane in lanes
421
+ )
422
+ object.__setattr__(self, "decision_slots", slots)
423
+ if type(slots) is not tuple or any(
424
+ type(value) is not CampaignDecisionSlot for value in slots
425
+ ):
426
+ raise TypeError(
427
+ "decision_slots must contain exact CampaignDecisionSlot values"
428
+ )
429
+ for slot in slots:
430
+ CampaignDecisionSlot.__post_init__(slot)
431
+ slot_ids = tuple(slot.slot_id for slot in slots)
432
+ if len(set(slot_ids)) != len(slot_ids):
433
+ raise ValueError("campaign decision slot IDs must be unique")
434
+ if tuple(slot.lane_id for slot in slots) != lane_ids:
435
+ raise ValueError(
436
+ "bounded campaign selection requires one ordered slot per lane"
437
+ )
438
+
439
+
440
+ _PARENT_SELECTION_PROGRESS_DOMAIN = (
441
+ b"agent-evolve:campaign-parent-selection-progress:v1\x00"
442
+ )
443
+
444
+
445
+ @dataclass(frozen=True, slots=True)
446
+ class CampaignParentSelectionProgress:
447
+ """Authenticated workload-neutral archive transition from one stage."""
448
+
449
+ generation: int
450
+ stage_kind: CampaignGenerationKind
451
+ stage_request_sha256: str
452
+ stage_receipt_sha256: str
453
+ pre_archive_sha256: str
454
+ post_archive_sha256: str
455
+ utility_id: str
456
+ utility_version: int
457
+ utility_definition_sha256: str
458
+ pre_utility_snapshot_sha256: str
459
+ post_utility_snapshot_sha256: str
460
+ pre_scalar_utility_hex: str | None
461
+ post_scalar_utility_hex: str | None
462
+
463
+ def __post_init__(self) -> None:
464
+ if type(self.generation) is not int or self.generation <= 0:
465
+ raise ValueError("progress generation must be a positive exact integer")
466
+ if type(self.stage_kind) is not CampaignGenerationKind:
467
+ raise TypeError("stage_kind must be an exact CampaignGenerationKind")
468
+ for name in (
469
+ "stage_request_sha256",
470
+ "stage_receipt_sha256",
471
+ "pre_archive_sha256",
472
+ "post_archive_sha256",
473
+ "utility_definition_sha256",
474
+ "pre_utility_snapshot_sha256",
475
+ "post_utility_snapshot_sha256",
476
+ ):
477
+ require_sha256(getattr(self, name), name)
478
+ _require_role_token(self.utility_id, "utility_id")
479
+ if type(self.utility_version) is not int or self.utility_version <= 0:
480
+ raise ValueError("utility_version must be a positive exact integer")
481
+ scalar_values = (
482
+ self.pre_scalar_utility_hex,
483
+ self.post_scalar_utility_hex,
484
+ )
485
+ if (scalar_values[0] is None) is not (scalar_values[1] is None):
486
+ raise ValueError("scalar utility progress must be complete or absent")
487
+ if scalar_values[0] is not None:
488
+ for name, value in zip(
489
+ ("pre_scalar_utility_hex", "post_scalar_utility_hex"),
490
+ scalar_values,
491
+ strict=True,
492
+ ):
493
+ if type(value) is not str:
494
+ raise TypeError(f"{name} must be an exact string or None")
495
+ try:
496
+ parsed = float.fromhex(value)
497
+ except ValueError as error:
498
+ raise ValueError(f"{name} must be a float hex string") from error
499
+ if not math.isfinite(parsed) or parsed < 0.0 or parsed.hex() != value:
500
+ raise ValueError(
501
+ f"{name} must encode a canonical non-negative utility"
502
+ )
503
+ if float.fromhex(scalar_values[1]) < float.fromhex(scalar_values[0]):
504
+ raise ValueError("authenticated archive utility cannot decrease")
505
+
506
+ @property
507
+ def archive_changed(self) -> bool:
508
+ return self.pre_archive_sha256 != self.post_archive_sha256
509
+
510
+ @property
511
+ def utility_signal_available(self) -> bool:
512
+ return self.pre_scalar_utility_hex is not None
513
+
514
+ @property
515
+ def utility_improved(self) -> bool | None:
516
+ if not self.utility_signal_available:
517
+ return None
518
+ assert self.pre_scalar_utility_hex is not None
519
+ assert self.post_scalar_utility_hex is not None
520
+ return float.fromhex(self.post_scalar_utility_hex) > float.fromhex(
521
+ self.pre_scalar_utility_hex
522
+ )
523
+
524
+ def _unsigned_record(self) -> dict[str, object]:
525
+ self.__post_init__()
526
+ return {
527
+ "generation": self.generation,
528
+ "stage_kind": self.stage_kind.value,
529
+ "stage_request_sha256": self.stage_request_sha256,
530
+ "stage_receipt_sha256": self.stage_receipt_sha256,
531
+ "pre_archive_sha256": self.pre_archive_sha256,
532
+ "post_archive_sha256": self.post_archive_sha256,
533
+ "archive_changed": self.archive_changed,
534
+ "utility_id": self.utility_id,
535
+ "utility_version": self.utility_version,
536
+ "utility_definition_sha256": self.utility_definition_sha256,
537
+ "pre_utility_snapshot_sha256": self.pre_utility_snapshot_sha256,
538
+ "post_utility_snapshot_sha256": self.post_utility_snapshot_sha256,
539
+ "pre_scalar_utility_hex": self.pre_scalar_utility_hex,
540
+ "post_scalar_utility_hex": self.post_scalar_utility_hex,
541
+ "utility_signal_available": self.utility_signal_available,
542
+ "utility_improved": self.utility_improved,
543
+ }
544
+
545
+ @property
546
+ def progress_sha256(self) -> str:
547
+ return hashlib.sha256(
548
+ _PARENT_SELECTION_PROGRESS_DOMAIN
549
+ + _canonical_text(self._unsigned_record()).encode("ascii")
550
+ ).hexdigest()
551
+
552
+ def to_record(self) -> dict[str, object]:
553
+ return {
554
+ **self._unsigned_record(),
555
+ "progress_sha256": self.progress_sha256,
556
+ }
557
+
558
+
559
+ @dataclass(frozen=True, slots=True)
560
+ class _PendingCampaignParentSelectionProgress:
561
+ generation: int
562
+ stage_kind: CampaignGenerationKind
563
+ stage_request_sha256: str
564
+ stage_receipt_sha256: str
565
+ pre_archive_sha256: str
566
+ post_archive_sha256: str
567
+ pre_utility: ArchiveUtilitySnapshot
568
+
569
+ def __post_init__(self) -> None:
570
+ if type(self.generation) is not int or self.generation <= 0:
571
+ raise ValueError("pending progress generation must be positive")
572
+ if type(self.stage_kind) is not CampaignGenerationKind:
573
+ raise TypeError("pending stage kind must be exact")
574
+ for name in (
575
+ "stage_request_sha256",
576
+ "stage_receipt_sha256",
577
+ "pre_archive_sha256",
578
+ "post_archive_sha256",
579
+ ):
580
+ require_sha256(getattr(self, name), name)
581
+ if type(self.pre_utility) is not ArchiveUtilitySnapshot:
582
+ raise TypeError("pre_utility must be an exact ArchiveUtilitySnapshot")
583
+ ArchiveUtilitySnapshot.__post_init__(self.pre_utility)
584
+
585
+ def complete(
586
+ self,
587
+ post_utility: ArchiveUtilitySnapshot,
588
+ ) -> CampaignParentSelectionProgress:
589
+ self.__post_init__()
590
+ if type(post_utility) is not ArchiveUtilitySnapshot:
591
+ raise TypeError("post_utility must be an exact ArchiveUtilitySnapshot")
592
+ ArchiveUtilitySnapshot.__post_init__(post_utility)
593
+ if (
594
+ post_utility.generation != self.generation + 1
595
+ or post_utility.utility_id != self.pre_utility.utility_id
596
+ or post_utility.utility_version != self.pre_utility.utility_version
597
+ or post_utility.definition_sha256 != self.pre_utility.definition_sha256
598
+ or post_utility.archive_sha256 != self.post_archive_sha256
599
+ ):
600
+ raise ValueError(
601
+ "post-stage utility snapshot does not close pending progress"
602
+ )
603
+ return CampaignParentSelectionProgress(
604
+ generation=self.generation,
605
+ stage_kind=self.stage_kind,
606
+ stage_request_sha256=self.stage_request_sha256,
607
+ stage_receipt_sha256=self.stage_receipt_sha256,
608
+ pre_archive_sha256=self.pre_archive_sha256,
609
+ post_archive_sha256=self.post_archive_sha256,
610
+ utility_id=self.pre_utility.utility_id,
611
+ utility_version=self.pre_utility.utility_version,
612
+ utility_definition_sha256=self.pre_utility.definition_sha256,
613
+ pre_utility_snapshot_sha256=self.pre_utility.snapshot_sha256,
614
+ post_utility_snapshot_sha256=post_utility.snapshot_sha256,
615
+ pre_scalar_utility_hex=self.pre_utility.scalar_utility_hex,
616
+ post_scalar_utility_hex=post_utility.scalar_utility_hex,
617
+ )
618
+
619
+
620
+ def _validate_parent_selection_progress(
621
+ progress: tuple[CampaignParentSelectionProgress, ...],
622
+ *,
623
+ optimizer_generation: int,
624
+ ) -> None:
625
+ if type(progress) is not tuple or any(
626
+ type(value) is not CampaignParentSelectionProgress for value in progress
627
+ ):
628
+ raise TypeError(
629
+ "progress must contain exact CampaignParentSelectionProgress values"
630
+ )
631
+ for value in progress:
632
+ CampaignParentSelectionProgress.__post_init__(value)
633
+ if tuple(value.generation for value in progress) != tuple(
634
+ range(1, len(progress) + 1)
635
+ ):
636
+ raise ValueError("parent-selection progress must be contiguous and ordered")
637
+ if len(progress) != optimizer_generation:
638
+ raise ValueError(
639
+ "parent-selection progress must cover the exact optimizer generation"
640
+ )
641
+
642
+
643
+ @runtime_checkable
644
+ class CampaignParentSelectionPort(Protocol):
645
+ """Experiment-owned, workload-neutral parent-selection seam."""
646
+
647
+ def select(
648
+ self,
649
+ state: OptimizerState,
650
+ *,
651
+ task_sha256: str,
652
+ parent_count: int,
653
+ rotation_index: int,
654
+ progress: tuple[CampaignParentSelectionProgress, ...] = (),
655
+ archive_utility: ArchiveUtilitySnapshot | None = None,
656
+ ) -> CampaignParentSelection: ...
657
+
658
+
659
+ @dataclass(frozen=True, slots=True)
660
+ class ArchiveReservoirCampaignParentSelector:
661
+ """Use the existing ranked-reservoir policy behind the campaign seam."""
662
+
663
+ reservoir_limit: int = 8
664
+ policy: TaskKeyedArchiveReservoirParentPolicy = field(
665
+ default_factory=TaskKeyedArchiveReservoirParentPolicy
666
+ )
667
+
668
+ def __post_init__(self) -> None:
669
+ if type(self.reservoir_limit) is not int or self.reservoir_limit <= 0:
670
+ raise ValueError("reservoir_limit must be positive")
671
+ if type(self.policy) is not TaskKeyedArchiveReservoirParentPolicy:
672
+ raise TypeError("policy must be an exact reservoir parent policy")
673
+
674
+ def select(
675
+ self,
676
+ state: OptimizerState,
677
+ *,
678
+ task_sha256: str,
679
+ parent_count: int,
680
+ rotation_index: int,
681
+ progress: tuple[CampaignParentSelectionProgress, ...] = (),
682
+ archive_utility: ArchiveUtilitySnapshot | None = None,
683
+ ) -> CampaignParentSelection:
684
+ del archive_utility
685
+ _validate_parent_selection_progress(
686
+ progress,
687
+ optimizer_generation=state.generation,
688
+ )
689
+ selected = self.policy.select(
690
+ state,
691
+ task_sha256=task_sha256,
692
+ expected_archive_snapshot_hash=state.archive_snapshot_hash,
693
+ reservoir_limit=self.reservoir_limit,
694
+ parent_count=parent_count,
695
+ rotation_index=rotation_index,
696
+ )
697
+ lanes = tuple(
698
+ CampaignParentLane(
699
+ lane_id=f"reservoir_{index + 1:04d}",
700
+ parent=parent,
701
+ )
702
+ for index, parent in enumerate(selected.parents)
703
+ )
704
+ return CampaignParentSelection(
705
+ parents=selected.parents,
706
+ evidence=_object(selected.receipt.to_trace_record()),
707
+ lanes=lanes,
708
+ )
709
+
710
+
711
+ @dataclass(frozen=True, slots=True)
712
+ class ArchiveEliteCampaignParentSelector:
713
+ """Use the outcome-relation-neutral archive-front policy in a campaign."""
714
+
715
+ policy: TaskKeyedArchiveEliteParentPolicy = field(
716
+ default_factory=TaskKeyedArchiveEliteParentPolicy
717
+ )
718
+
719
+ def __post_init__(self) -> None:
720
+ if type(self.policy) is not TaskKeyedArchiveEliteParentPolicy:
721
+ raise TypeError("policy must be an exact elite parent policy")
722
+
723
+ def select(
724
+ self,
725
+ state: OptimizerState,
726
+ *,
727
+ task_sha256: str,
728
+ parent_count: int,
729
+ rotation_index: int,
730
+ progress: tuple[CampaignParentSelectionProgress, ...] = (),
731
+ archive_utility: ArchiveUtilitySnapshot | None = None,
732
+ ) -> CampaignParentSelection:
733
+ del archive_utility
734
+ _validate_parent_selection_progress(
735
+ progress,
736
+ optimizer_generation=state.generation,
737
+ )
738
+ selected = self.policy.select(
739
+ state,
740
+ task_sha256=task_sha256,
741
+ expected_archive_snapshot_hash=state.archive_snapshot_hash,
742
+ parent_count=parent_count,
743
+ rotation_index=rotation_index,
744
+ )
745
+ lanes = tuple(
746
+ CampaignParentLane(
747
+ lane_id=f"elite_{index + 1:04d}",
748
+ parent=parent,
749
+ )
750
+ for index, parent in enumerate(selected.parents)
751
+ )
752
+ return CampaignParentSelection(
753
+ parents=selected.parents,
754
+ evidence=_object(selected.receipt.to_trace_record()),
755
+ lanes=lanes,
756
+ )
757
+
758
+
759
+ _DIVERSE_ELITE_PARENT_POLICY_ID = "archive_diverse_elite_campaign_parent"
760
+ _DIVERSE_ELITE_PARENT_POLICY_VERSION = 1
761
+ _DIVERSE_ELITE_PARENT_POLICY_DEFINITION_SHA256 = hashlib.sha256(
762
+ b"agent-evolve:archive-diverse-elite-campaign-parent:v1:"
763
+ b"elite=task-keyed-current-front-member;"
764
+ b"explorer=max-canonical-typed-patch-operation-distance-on-current-front;"
765
+ b"ties=task-and-snapshot-keyed-sha256;"
766
+ b"singleton=structurally-most-distant-eligible-history-fallback;"
767
+ b"workload-semantics=false;dominated-parent=false"
768
+ ).hexdigest()
769
+ _DIVERSE_ELITE_PARENT_ROTATION_DOMAIN = (
770
+ b"agent-evolve:archive-diverse-elite-campaign-parent:rotation:v1\x00"
771
+ )
772
+ _DIVERSE_ELITE_PARENT_TIE_DOMAIN = (
773
+ b"agent-evolve:archive-diverse-elite-campaign-parent:tie:v1\x00"
774
+ )
775
+
776
+
777
+ @dataclass(frozen=True, slots=True)
778
+ class ArchiveDiverseEliteCampaignParentSelector:
779
+ """Select two nondominated parents with workload-neutral structural spread.
780
+
781
+ The first lane rotates over the authenticated current Pareto front. The
782
+ second lane is the distinct current-front configuration requiring the
783
+ largest canonical typed-patch operation set from that anchor. This keeps
784
+ exploration on known trade-off-efficient material while exposing generic
785
+ structural diversity to mutation and crossover. A singleton front falls
786
+ back explicitly to the structurally most distant eligible history member,
787
+ because campaign parent occurrences must remain distinct.
788
+ """
789
+
790
+ def select(
791
+ self,
792
+ state: OptimizerState,
793
+ *,
794
+ task_sha256: str,
795
+ parent_count: int,
796
+ rotation_index: int,
797
+ progress: tuple[CampaignParentSelectionProgress, ...] = (),
798
+ archive_utility: ArchiveUtilitySnapshot | None = None,
799
+ ) -> CampaignParentSelection:
800
+ del archive_utility
801
+ if type(self) is not ArchiveDiverseEliteCampaignParentSelector:
802
+ raise TypeError("selector must be an exact diverse-elite selector")
803
+ OptimizerState.__post_init__(state)
804
+ _validate_parent_selection_progress(
805
+ progress,
806
+ optimizer_generation=state.generation,
807
+ )
808
+ require_sha256(task_sha256, "task_sha256")
809
+ if type(parent_count) is not int or parent_count != 2:
810
+ raise ValueError(
811
+ "diverse-elite campaign parent selection requires exactly two lanes"
812
+ )
813
+ if type(rotation_index) is not int or not 0 <= rotation_index < (1 << 63):
814
+ raise ValueError("rotation_index must be an exact non-negative int63")
815
+
816
+ candidates = state.archive.front_candidates
817
+ references = state.archive.front_references
818
+ if not candidates or len(candidates) != len(references):
819
+ raise ValueError("current archive front is empty or internally misaligned")
820
+ by_id = {candidate.candidate_id: candidate for candidate in candidates}
821
+ if (
822
+ tuple(by_id[reference.candidate_id] for reference in references)
823
+ != candidates
824
+ ):
825
+ raise ValueError("current archive candidates differ from front references")
826
+
827
+ rotation_digest = hashlib.sha256(
828
+ _DIVERSE_ELITE_PARENT_ROTATION_DOMAIN
829
+ + bytes.fromhex(task_sha256)
830
+ + bytes.fromhex(state.archive_snapshot_hash)
831
+ ).digest()
832
+ rotation_anchor = int.from_bytes(rotation_digest, "big") % len(candidates)
833
+ elite_ordinal = (rotation_anchor + rotation_index) % len(candidates)
834
+ elite = candidates[elite_ordinal]
835
+
836
+ distance_records: list[dict[str, object]] = []
837
+ if len(candidates) == 1:
838
+ explorer_pool = tuple(
839
+ candidate
840
+ for candidate in state.candidates
841
+ if candidate.candidate_id != elite.candidate_id
842
+ and candidate.occurrence.configuration_hash
843
+ != elite.occurrence.configuration_hash
844
+ and candidate.valid
845
+ and candidate.operator_compliant
846
+ and candidate.evidence_compliant
847
+ and candidate.objectives
848
+ )
849
+ if not explorer_pool:
850
+ raise ValueError(
851
+ "diverse-elite selection requires two eligible distinct "
852
+ "candidate occurrences"
853
+ )
854
+ explorer_sources = tuple(
855
+ (len(candidates) + index, candidate)
856
+ for index, candidate in enumerate(explorer_pool)
857
+ )
858
+ fallback_reason = "singleton_front_structural_history_fallback"
859
+ else:
860
+ explorer_sources = tuple(
861
+ (ordinal, candidate)
862
+ for ordinal, candidate in enumerate(candidates)
863
+ if ordinal != elite_ordinal
864
+ )
865
+ fallback_reason = "none"
866
+
867
+ scored: list[tuple[int, bytes, int, EvolutionCandidate, str]] = []
868
+ for ordinal, candidate in explorer_sources:
869
+ patch = derive_patch(
870
+ elite.configuration,
871
+ candidate.configuration,
872
+ base_candidate_id=elite.candidate_id,
873
+ target_candidate_id=candidate.candidate_id,
874
+ )
875
+ operation_distance = len(patch.operations)
876
+ tie_digest = hashlib.sha256(
877
+ _DIVERSE_ELITE_PARENT_TIE_DOMAIN
878
+ + bytes.fromhex(task_sha256)
879
+ + bytes.fromhex(state.archive_snapshot_hash)
880
+ + rotation_index.to_bytes(8, "big", signed=False)
881
+ + bytes.fromhex(candidate.occurrence.configuration_hash)
882
+ ).digest()
883
+ scored.append(
884
+ (
885
+ operation_distance,
886
+ tie_digest,
887
+ ordinal,
888
+ candidate,
889
+ patch.patch_hash,
890
+ )
891
+ )
892
+ distance_records.append(
893
+ {
894
+ "candidate_id": candidate.candidate_id.value,
895
+ "configuration_hash": candidate.occurrence.configuration_hash,
896
+ "typed_patch_operation_distance": operation_distance,
897
+ "typed_patch_sha256": patch.patch_hash,
898
+ }
899
+ )
900
+ maximum_distance = max(value[0] for value in scored)
901
+ finalists = tuple(value for value in scored if value[0] == maximum_distance)
902
+ selected = min(finalists, key=lambda value: (value[1], value[2]))
903
+ _, _, explorer_ordinal, explorer, _ = selected
904
+
905
+ selected_parents = (elite, explorer)
906
+ lanes = (
907
+ CampaignParentLane(lane_id="elite", parent=elite),
908
+ CampaignParentLane(lane_id="explorer", parent=explorer),
909
+ )
910
+ return CampaignParentSelection(
911
+ parents=selected_parents,
912
+ lanes=lanes,
913
+ evidence=_object(
914
+ {
915
+ "schema_version": 1,
916
+ "policy_id": _DIVERSE_ELITE_PARENT_POLICY_ID,
917
+ "policy_version": _DIVERSE_ELITE_PARENT_POLICY_VERSION,
918
+ "policy_definition_sha256": (
919
+ _DIVERSE_ELITE_PARENT_POLICY_DEFINITION_SHA256
920
+ ),
921
+ "task_sha256": task_sha256,
922
+ "optimizer_generation": state.generation,
923
+ "archive_snapshot_hash": state.archive_snapshot_hash,
924
+ "eligible_front": [
925
+ reference.to_trace_record() for reference in references
926
+ ],
927
+ "rotation_index": rotation_index,
928
+ "rotation_anchor": rotation_anchor,
929
+ "elite_ordinal": elite_ordinal,
930
+ "explorer_ordinal": explorer_ordinal,
931
+ "maximum_typed_patch_operation_distance": maximum_distance,
932
+ "distance_evidence": sorted(
933
+ distance_records,
934
+ key=lambda value: str(value["candidate_id"]),
935
+ ),
936
+ "fallback_reason": fallback_reason,
937
+ "selected_parent_ids": [
938
+ parent.candidate_id.value for parent in selected_parents
939
+ ],
940
+ "selected_parents_are_current_front_members": all(
941
+ parent.candidate_id in by_id for parent in selected_parents
942
+ ),
943
+ "provider_fields_consulted": False,
944
+ "workload_semantics_consulted": False,
945
+ }
946
+ ),
947
+ )
948
+
949
+
950
+ _STAGNATION_AWARE_PARENT_POLICY_ID = "stagnation_aware_diverse_campaign_parent"
951
+ _STAGNATION_AWARE_PARENT_POLICY_VERSION = 1
952
+ _STAGNATION_AWARE_PARENT_WINDOW = 2
953
+ _STAGNATION_AWARE_PARENT_POLICY_DEFINITION_SHA256 = hashlib.sha256(
954
+ b"agent-evolve:stagnation-aware-diverse-campaign-parent:v1:"
955
+ b"normal=archive-diverse-elite-campaign-parent-v1;"
956
+ b"trigger=two-consecutive-runtime-authenticated-stage-progress-receipts-with-"
957
+ b"no-increase-in-generic-scalar-archive-utility;"
958
+ b"elite=normal-policy-elite;"
959
+ b"stagnant-explorer=eligible-nonfront-history-member-maximizing-minimum-"
960
+ b"canonical-typed-patch-operation-distance-to-current-front;"
961
+ b"ties=task-snapshot-rotation-and-configuration-keyed-sha256;"
962
+ b"empty-pool=normal-policy-fallback;"
963
+ b"provider-fields=false;workload-semantics=false"
964
+ ).hexdigest()
965
+ _STAGNATION_AWARE_PARENT_TIE_DOMAIN = (
966
+ b"agent-evolve:stagnation-aware-diverse-campaign-parent:tie:v1\x00"
967
+ )
968
+
969
+
970
+ @dataclass(frozen=True, slots=True)
971
+ class StagnationAwareDiverseCampaignParentSelector:
972
+ """Switch one parent lane to structurally remote history after stagnation.
973
+
974
+ Normal generations use :class:`ArchiveDiverseEliteCampaignParentSelector`
975
+ exactly. Once the two most recent authenticated generation receipts both
976
+ report an unchanged archive snapshot, the elite lane remains untouched and
977
+ the explorer lane is sourced from eligible evaluated history outside the
978
+ current front. Its score is the minimum canonical typed-patch operation
979
+ distance to *every* current-front member, so the switch seeks a genuinely
980
+ different basin rather than a point remote from only one arbitrary anchor.
981
+
982
+ The detector and distance law consume no objective names, workload
983
+ semantics, prompts, provider fields, or model identity. If no eligible
984
+ nonfront source exists, selection falls back explicitly to the normal
985
+ diverse-elite policy and records that fact.
986
+ """
987
+
988
+ policy_id = _STAGNATION_AWARE_PARENT_POLICY_ID
989
+ policy_version = _STAGNATION_AWARE_PARENT_POLICY_VERSION
990
+ definition_sha256 = _STAGNATION_AWARE_PARENT_POLICY_DEFINITION_SHA256
991
+ stagnation_window = _STAGNATION_AWARE_PARENT_WINDOW
992
+
993
+ def select(
994
+ self,
995
+ state: OptimizerState,
996
+ *,
997
+ task_sha256: str,
998
+ parent_count: int,
999
+ rotation_index: int,
1000
+ progress: tuple[CampaignParentSelectionProgress, ...] = (),
1001
+ archive_utility: ArchiveUtilitySnapshot | None = None,
1002
+ ) -> CampaignParentSelection:
1003
+ if type(self) is not StagnationAwareDiverseCampaignParentSelector:
1004
+ raise TypeError("selector must be an exact stagnation-aware selector")
1005
+ OptimizerState.__post_init__(state)
1006
+ _validate_parent_selection_progress(
1007
+ progress,
1008
+ optimizer_generation=state.generation,
1009
+ )
1010
+ require_sha256(task_sha256, "task_sha256")
1011
+ if type(parent_count) is not int or parent_count != 2:
1012
+ raise ValueError(
1013
+ "stagnation-aware campaign parent selection requires exactly two lanes"
1014
+ )
1015
+ if type(rotation_index) is not int or not 0 <= rotation_index < (1 << 63):
1016
+ raise ValueError("rotation_index must be an exact non-negative int63")
1017
+
1018
+ normal = ArchiveDiverseEliteCampaignParentSelector().select(
1019
+ state,
1020
+ task_sha256=task_sha256,
1021
+ parent_count=parent_count,
1022
+ rotation_index=rotation_index,
1023
+ progress=progress,
1024
+ archive_utility=archive_utility,
1025
+ )
1026
+ receipt_window = progress[-self.stagnation_window :]
1027
+ if any(not receipt.utility_signal_available for receipt in progress):
1028
+ raise ValueError(
1029
+ "stagnation-aware selection requires authenticated scalar "
1030
+ "archive utility on every completed stage"
1031
+ )
1032
+ receipt_evidence = [receipt.to_record() for receipt in receipt_window]
1033
+ stagnation_triggered = len(receipt_window) == self.stagnation_window and all(
1034
+ receipt.utility_improved is False for receipt in receipt_window
1035
+ )
1036
+
1037
+ selected_parents = normal.parents
1038
+ source_mode = "normal_diverse_elite"
1039
+ source_switch_applied = False
1040
+ selected_history_ordinal: int | None = None
1041
+ maximum_minimum_distance: int | None = None
1042
+ distance_records: list[dict[str, object]] = []
1043
+
1044
+ front = state.archive.front_candidates
1045
+ front_ids = {candidate.candidate_id for candidate in front}
1046
+ front_configuration_hashes = {
1047
+ candidate.occurrence.configuration_hash for candidate in front
1048
+ }
1049
+ eligible_history = tuple(
1050
+ (ordinal, candidate)
1051
+ for ordinal, candidate in enumerate(state.candidates)
1052
+ if candidate.candidate_id not in front_ids
1053
+ and candidate.occurrence.configuration_hash
1054
+ not in front_configuration_hashes
1055
+ and candidate.valid
1056
+ and candidate.operator_compliant
1057
+ and candidate.evidence_compliant
1058
+ and candidate.objectives
1059
+ )
1060
+
1061
+ if stagnation_triggered and eligible_history:
1062
+ scored: list[tuple[int, bytes, int, EvolutionCandidate]] = []
1063
+ for history_ordinal, candidate in eligible_history:
1064
+ front_distances: list[dict[str, object]] = []
1065
+ operation_distances: list[int] = []
1066
+ for front_candidate in front:
1067
+ patch = derive_patch(
1068
+ front_candidate.configuration,
1069
+ candidate.configuration,
1070
+ base_candidate_id=front_candidate.candidate_id,
1071
+ target_candidate_id=candidate.candidate_id,
1072
+ )
1073
+ operation_distance = len(patch.operations)
1074
+ operation_distances.append(operation_distance)
1075
+ front_distances.append(
1076
+ {
1077
+ "front_candidate_id": (front_candidate.candidate_id.value),
1078
+ "front_configuration_sha256": (
1079
+ front_candidate.occurrence.configuration_hash
1080
+ ),
1081
+ "typed_patch_operation_distance": operation_distance,
1082
+ "typed_patch_sha256": patch.patch_hash,
1083
+ }
1084
+ )
1085
+ minimum_distance = min(operation_distances)
1086
+ tie_digest = hashlib.sha256(
1087
+ _STAGNATION_AWARE_PARENT_TIE_DOMAIN
1088
+ + bytes.fromhex(task_sha256)
1089
+ + bytes.fromhex(state.archive_snapshot_hash)
1090
+ + rotation_index.to_bytes(8, "big", signed=False)
1091
+ + bytes.fromhex(candidate.occurrence.configuration_hash)
1092
+ ).digest()
1093
+ scored.append(
1094
+ (minimum_distance, tie_digest, history_ordinal, candidate)
1095
+ )
1096
+ distance_records.append(
1097
+ {
1098
+ "candidate_id": candidate.candidate_id.value,
1099
+ "configuration_sha256": (
1100
+ candidate.occurrence.configuration_hash
1101
+ ),
1102
+ "history_ordinal": history_ordinal,
1103
+ "minimum_typed_patch_operation_distance_to_front": (
1104
+ minimum_distance
1105
+ ),
1106
+ "front_distance_evidence": sorted(
1107
+ front_distances,
1108
+ key=lambda value: str(value["front_candidate_id"]),
1109
+ ),
1110
+ }
1111
+ )
1112
+ maximum_minimum_distance = max(value[0] for value in scored)
1113
+ finalists = tuple(
1114
+ value for value in scored if value[0] == maximum_minimum_distance
1115
+ )
1116
+ _, _, selected_history_ordinal, explorer = min(
1117
+ finalists,
1118
+ key=lambda value: (value[1], value[2]),
1119
+ )
1120
+ selected_parents = (normal.parents[0], explorer)
1121
+ source_mode = "stagnation_remote_history"
1122
+ source_switch_applied = True
1123
+ elif stagnation_triggered:
1124
+ source_mode = "stagnation_triggered_normal_fallback"
1125
+
1126
+ lanes = (
1127
+ CampaignParentLane(lane_id="elite", parent=selected_parents[0]),
1128
+ CampaignParentLane(lane_id="explorer", parent=selected_parents[1]),
1129
+ )
1130
+ return CampaignParentSelection(
1131
+ parents=selected_parents,
1132
+ lanes=lanes,
1133
+ evidence=_object(
1134
+ {
1135
+ "schema_version": 1,
1136
+ "policy_id": self.policy_id,
1137
+ "policy_version": self.policy_version,
1138
+ "policy_definition_sha256": self.definition_sha256,
1139
+ "task_sha256": task_sha256,
1140
+ "optimizer_generation": state.generation,
1141
+ "archive_snapshot_sha256": state.archive_snapshot_hash,
1142
+ "rotation_index": rotation_index,
1143
+ "stagnation_window": self.stagnation_window,
1144
+ "observed_receipt_count": len(receipt_window),
1145
+ "stagnation_receipt_evidence": receipt_evidence,
1146
+ "stagnation_triggered": stagnation_triggered,
1147
+ "source_switch_applied": source_switch_applied,
1148
+ "source_mode": source_mode,
1149
+ "eligible_nonfront_history_count": len(eligible_history),
1150
+ "maximum_minimum_typed_patch_operation_distance_to_front": (
1151
+ maximum_minimum_distance
1152
+ ),
1153
+ "selected_history_ordinal": selected_history_ordinal,
1154
+ "distance_evidence": sorted(
1155
+ distance_records,
1156
+ key=lambda value: str(value["candidate_id"]),
1157
+ ),
1158
+ "normal_policy_evidence": thaw_json(normal.evidence),
1159
+ "selected_parent_ids": [
1160
+ parent.candidate_id.value for parent in selected_parents
1161
+ ],
1162
+ "selected_parent_configuration_sha256s": [
1163
+ parent.occurrence.configuration_hash
1164
+ for parent in selected_parents
1165
+ ],
1166
+ "selected_parents_are_current_front_members": all(
1167
+ parent.candidate_id in front_ids for parent in selected_parents
1168
+ ),
1169
+ "provider_fields_consulted": False,
1170
+ "workload_semantics_consulted": False,
1171
+ "objective_names_consulted": False,
1172
+ }
1173
+ ),
1174
+ )
1175
+
1176
+
1177
+ _RESIDUAL_FRONTIER_PARENT_POLICY_ID = "residual_hypervolume_campaign_parent"
1178
+ _RESIDUAL_FRONTIER_PARENT_POLICY_VERSION = 1
1179
+ _RESIDUAL_FRONTIER_PARENT_POLICY_DEFINITION_SHA256 = hashlib.sha256(
1180
+ b"agent-evolve:residual-hypervolume-campaign-parent:v1;"
1181
+ b"input=current-front-plus-authenticated-affine-prior-archive;"
1182
+ b"target=largest-positive-pairwise-midpoint-hypervolume-residual;"
1183
+ b"parents=distinct-current-front-members-nearest-two-cell-anchors;"
1184
+ b"fallback=stagnation-aware-diverse-parent-when-no-positive-cell;"
1185
+ b"lanes=stable-elite-explorer-identifiers-for-contract-compatibility;"
1186
+ b"current-future-outcomes=false;workload-model-provider-fields=false"
1187
+ ).hexdigest()
1188
+
1189
+
1190
+ @dataclass(frozen=True, slots=True)
1191
+ class ResidualHypervolumeCampaignParentSelector:
1192
+ """Coordinate two parent lanes around the largest missing frontier cell."""
1193
+
1194
+ policy_id = _RESIDUAL_FRONTIER_PARENT_POLICY_ID
1195
+ policy_version = _RESIDUAL_FRONTIER_PARENT_POLICY_VERSION
1196
+ definition_sha256 = _RESIDUAL_FRONTIER_PARENT_POLICY_DEFINITION_SHA256
1197
+
1198
+ def select(
1199
+ self,
1200
+ state: OptimizerState,
1201
+ *,
1202
+ task_sha256: str,
1203
+ parent_count: int,
1204
+ rotation_index: int,
1205
+ progress: tuple[CampaignParentSelectionProgress, ...] = (),
1206
+ archive_utility: ArchiveUtilitySnapshot | None = None,
1207
+ ) -> CampaignParentSelection:
1208
+ if type(self) is not ResidualHypervolumeCampaignParentSelector:
1209
+ raise TypeError("selector must be an exact residual-frontier selector")
1210
+ OptimizerState.__post_init__(state)
1211
+ _validate_parent_selection_progress(
1212
+ progress,
1213
+ optimizer_generation=state.generation,
1214
+ )
1215
+ require_sha256(task_sha256, "task_sha256")
1216
+ if type(parent_count) is not int or parent_count != 2:
1217
+ raise ValueError(
1218
+ "residual-frontier campaign parent selection requires two lanes"
1219
+ )
1220
+ if type(rotation_index) is not int or not 0 <= rotation_index < (1 << 63):
1221
+ raise ValueError("rotation_index must be an exact non-negative int63")
1222
+ if type(archive_utility) is not ArchiveUtilitySnapshot:
1223
+ raise TypeError(
1224
+ "residual-frontier parent selection requires archive_utility"
1225
+ )
1226
+ archive_utility.__post_init__()
1227
+ if archive_utility.generation != state.generation + 1:
1228
+ raise ValueError(
1229
+ "residual-frontier archive utility is stale for optimizer state"
1230
+ )
1231
+
1232
+ geometry = residual_frontier_geometry(archive_utility)
1233
+ selected = residual_anchor_parents(
1234
+ geometry=geometry,
1235
+ candidates=state.archive.front_candidates,
1236
+ )
1237
+ if selected is None:
1238
+ fallback = StagnationAwareDiverseCampaignParentSelector().select(
1239
+ state,
1240
+ task_sha256=task_sha256,
1241
+ parent_count=parent_count,
1242
+ rotation_index=rotation_index,
1243
+ progress=progress,
1244
+ archive_utility=archive_utility,
1245
+ )
1246
+ return CampaignParentSelection(
1247
+ parents=fallback.parents,
1248
+ lanes=fallback.lanes,
1249
+ decision_slots=fallback.decision_slots,
1250
+ evidence=_object(
1251
+ {
1252
+ "schema_version": 1,
1253
+ "policy_id": self.policy_id,
1254
+ "policy_version": self.policy_version,
1255
+ "policy_definition_sha256": self.definition_sha256,
1256
+ "residual_geometry_sha256": geometry.geometry_sha256,
1257
+ "residual_cell_count": len(geometry.cells),
1258
+ "source_mode": "no_positive_residual_fallback",
1259
+ "fallback_evidence": thaw_json(fallback.evidence),
1260
+ "current_or_future_candidate_outcomes_consulted": False,
1261
+ "workload_model_provider_fields_consulted": False,
1262
+ }
1263
+ ),
1264
+ )
1265
+
1266
+ front_ids = {value.candidate_id for value in state.archive.front_candidates}
1267
+ if any(value.candidate_id not in front_ids for value in selected):
1268
+ raise ValueError("residual-frontier parents escaped the current front")
1269
+ best_cell = geometry.cells[0]
1270
+ lanes = (
1271
+ CampaignParentLane(lane_id="elite", parent=selected[0]),
1272
+ CampaignParentLane(lane_id="explorer", parent=selected[1]),
1273
+ )
1274
+ return CampaignParentSelection(
1275
+ parents=selected,
1276
+ lanes=lanes,
1277
+ evidence=_object(
1278
+ {
1279
+ "schema_version": 1,
1280
+ "policy_id": self.policy_id,
1281
+ "policy_version": self.policy_version,
1282
+ "policy_definition_sha256": self.definition_sha256,
1283
+ "residual_core_definition_sha256": (
1284
+ RESIDUAL_FRONTIER_POLICY_DEFINITION_SHA256
1285
+ ),
1286
+ "task_sha256": task_sha256,
1287
+ "optimizer_generation": state.generation,
1288
+ "archive_snapshot_sha256": state.archive_snapshot_hash,
1289
+ "archive_utility_snapshot_sha256": (
1290
+ archive_utility.snapshot_sha256
1291
+ ),
1292
+ "rotation_index": rotation_index,
1293
+ "residual_geometry_sha256": geometry.geometry_sha256,
1294
+ "residual_cell_count": len(geometry.cells),
1295
+ "selected_cell": best_cell.to_record(),
1296
+ "selected_parent_ids": [
1297
+ value.candidate_id.value for value in selected
1298
+ ],
1299
+ "selected_parent_configuration_sha256s": [
1300
+ value.occurrence.configuration_hash for value in selected
1301
+ ],
1302
+ "source_mode": "largest_positive_residual_cell_anchors",
1303
+ "current_or_future_candidate_outcomes_consulted": False,
1304
+ "workload_model_provider_fields_consulted": False,
1305
+ }
1306
+ ),
1307
+ )
1308
+
1309
+
1310
+ @dataclass(frozen=True, slots=True)
1311
+ class ArchiveEliteExplorerCampaignParentSelector:
1312
+ """Bind the generic named elite/explorer lanes to campaign execution.
1313
+
1314
+ The underlying policy always returns exactly two parents: one current-front
1315
+ elite and one best-rank dominated explorer, with explicit distinct-front or
1316
+ singleton fallback. Keeping that fixed cardinality at this adapter makes a
1317
+ campaign's lane semantics impossible to erase by requesting an arbitrary
1318
+ parent count.
1319
+ """
1320
+
1321
+ policy: TaskKeyedArchiveEliteExplorerParentPolicy = field(
1322
+ default_factory=TaskKeyedArchiveEliteExplorerParentPolicy
1323
+ )
1324
+
1325
+ def __post_init__(self) -> None:
1326
+ if type(self.policy) is not TaskKeyedArchiveEliteExplorerParentPolicy:
1327
+ raise TypeError("policy must be an exact elite/explorer parent policy")
1328
+
1329
+ def select(
1330
+ self,
1331
+ state: OptimizerState,
1332
+ *,
1333
+ task_sha256: str,
1334
+ parent_count: int,
1335
+ rotation_index: int,
1336
+ progress: tuple[CampaignParentSelectionProgress, ...] = (),
1337
+ archive_utility: ArchiveUtilitySnapshot | None = None,
1338
+ ) -> CampaignParentSelection:
1339
+ del archive_utility
1340
+ _validate_parent_selection_progress(
1341
+ progress,
1342
+ optimizer_generation=state.generation,
1343
+ )
1344
+ if type(parent_count) is not int or parent_count != 2:
1345
+ raise ValueError(
1346
+ "elite/explorer campaign parent selection requires exactly two lanes"
1347
+ )
1348
+ selected = self.policy.select(
1349
+ state,
1350
+ task_sha256=task_sha256,
1351
+ expected_archive_snapshot_hash=state.archive_snapshot_hash,
1352
+ rotation_index=rotation_index,
1353
+ )
1354
+ lanes = tuple(
1355
+ CampaignParentLane(lane_id=lane_id, parent=parent)
1356
+ for lane_id, parent in zip(
1357
+ ("elite", "explorer"),
1358
+ selected.parents,
1359
+ strict=True,
1360
+ )
1361
+ )
1362
+ return CampaignParentSelection(
1363
+ parents=selected.parents,
1364
+ evidence=_object(selected.receipt.to_trace_record()),
1365
+ lanes=lanes,
1366
+ )
1367
+
1368
+
1369
+ @dataclass(frozen=True, slots=True)
1370
+ class CampaignPortfolioWaveContext:
1371
+ """Complete trusted input to one prompt/card treatment construction."""
1372
+
1373
+ prepared: PreparedEvolutionCampaign
1374
+ stage_request: CampaignStageRequest
1375
+ parent_slot: int
1376
+ parent: EvolutionCandidate
1377
+ variation: ParentVariationBinding
1378
+ evidence_context: FrozenJsonObject
1379
+ evidence_cards: tuple[FrozenJsonObject, ...]
1380
+ memory: FrozenJsonObject
1381
+ parent_measurement: ParentMeasurementBinding | None = None
1382
+ archive_context: CampaignPortfolioArchiveContextProjection | None = None
1383
+ test_eligible_reflections: tuple[tuple[str, FrozenJsonObject], ...] = ()
1384
+ parent_lane: CampaignParentLane | None = None
1385
+ decision_slot: CampaignDecisionSlot | None = None
1386
+ contextual_allocation: ContextualPortfolioAllocationContract | None = None
1387
+ frontier_target: CampaignPortfolioFrontierTarget | None = None
1388
+
1389
+ def __post_init__(self) -> None:
1390
+ if type(self.prepared) is not PreparedEvolutionCampaign:
1391
+ raise TypeError("prepared must be exact")
1392
+ if type(self.stage_request) is not CampaignStageRequest:
1393
+ raise TypeError("stage_request must be exact")
1394
+ if self.stage_request.step.kind is not CampaignGenerationKind.PORTFOLIO:
1395
+ raise ValueError("portfolio context requires a portfolio stage")
1396
+ if type(self.parent_slot) is not int or self.parent_slot < 0:
1397
+ raise ValueError("parent_slot must be non-negative")
1398
+ if type(self.parent) is not EvolutionCandidate:
1399
+ raise TypeError("parent must be exact")
1400
+ if type(self.variation) is not ParentVariationBinding:
1401
+ raise TypeError("variation must be exact")
1402
+ lane = self.parent_lane
1403
+ slot = self.decision_slot
1404
+ if lane is None and slot is None:
1405
+ lane = CampaignParentLane(
1406
+ lane_id=f"parent_{self.parent_slot + 1:04d}",
1407
+ parent=self.parent,
1408
+ )
1409
+ slot = CampaignDecisionSlot(
1410
+ slot_id=f"{lane.lane_id}.primary",
1411
+ lane_id=lane.lane_id,
1412
+ )
1413
+ object.__setattr__(self, "parent_lane", lane)
1414
+ object.__setattr__(self, "decision_slot", slot)
1415
+ elif lane is None or slot is None:
1416
+ raise ValueError("parent_lane and decision_slot must be supplied together")
1417
+ if type(lane) is not CampaignParentLane:
1418
+ raise TypeError("parent_lane must be exact or None")
1419
+ if type(slot) is not CampaignDecisionSlot:
1420
+ raise TypeError("decision_slot must be exact or None")
1421
+ CampaignParentLane.__post_init__(lane)
1422
+ CampaignDecisionSlot.__post_init__(slot)
1423
+ if lane.parent is not self.parent or slot.lane_id != lane.lane_id:
1424
+ raise ValueError("decision slot, parent lane, and parent differ")
1425
+ allocation = self.contextual_allocation
1426
+ if allocation is not None:
1427
+ if type(allocation) is not ContextualPortfolioAllocationContract:
1428
+ raise TypeError("contextual_allocation must be exact or None")
1429
+ allocation.__post_init__()
1430
+ if (
1431
+ allocation.campaign_generation != self.stage_request.step.generation
1432
+ or allocation.slice_id != lane.lane_id
1433
+ or allocation.evaluation_slots != self.prepared.protocol.portfolio_width
1434
+ ):
1435
+ raise ValueError("contextual allocation differs from its campaign lane")
1436
+ for name in ("evidence_context", "memory"):
1437
+ value = getattr(self, name)
1438
+ if type(value) is not FrozenJsonObject or freeze_json(value) is not value:
1439
+ raise TypeError(f"{name} must be an exact frozen object")
1440
+ evidence_values = dict(self.evidence_context.items)
1441
+ if CAMPAIGN_SELECTOR_CONTEXT_EXTENSION_KEY in evidence_values:
1442
+ raise ValueError(
1443
+ "trusted evidence context uses the reserved selector extension key"
1444
+ )
1445
+ archive_context = self.archive_context
1446
+ archive_payload = evidence_values.get(CAMPAIGN_ARCHIVE_CONTEXT_KEY)
1447
+ if archive_context is not None:
1448
+ if type(archive_context) is not CampaignPortfolioArchiveContextProjection:
1449
+ raise TypeError("archive_context must be exact or None")
1450
+ archive_context.__post_init__()
1451
+ if (
1452
+ archive_context.archive_utility_snapshot_sha256
1453
+ != self.stage_request.archive_utility.snapshot_sha256
1454
+ or archive_context.parent_configuration_sha256
1455
+ != self.parent.occurrence.configuration_hash
1456
+ ):
1457
+ raise ValueError("archive context differs from its campaign lane")
1458
+ if (
1459
+ archive_payload is None
1460
+ or thaw_json(archive_payload) != archive_context.to_record()
1461
+ ):
1462
+ raise ValueError(
1463
+ "archive context payload differs from its authenticated receipt"
1464
+ )
1465
+ target = self.frontier_target
1466
+ target_payload = evidence_values.get(CAMPAIGN_FRONTIER_TARGET_KEY)
1467
+ if target is None:
1468
+ if target_payload is not None:
1469
+ raise ValueError(
1470
+ "trusted evidence context uses an unauthenticated frontier target"
1471
+ )
1472
+ else:
1473
+ if type(target) is not CampaignPortfolioFrontierTarget:
1474
+ raise TypeError("frontier_target must be exact or None")
1475
+ target.__post_init__()
1476
+ if (
1477
+ target.lane_id != lane.lane_id
1478
+ or target.parent_configuration_sha256
1479
+ != self.parent.occurrence.configuration_hash
1480
+ or target.archive_utility_snapshot_sha256
1481
+ != self.stage_request.archive_utility.snapshot_sha256
1482
+ ):
1483
+ raise ValueError("frontier target differs from its campaign lane")
1484
+ if (
1485
+ target_payload is None
1486
+ or thaw_json(target_payload) != target.to_record()
1487
+ ):
1488
+ raise ValueError(
1489
+ "frontier target payload differs from its authenticated receipt"
1490
+ )
1491
+ if type(self.evidence_cards) is not tuple or not self.evidence_cards:
1492
+ raise ValueError("portfolio context requires evidence cards")
1493
+ if any(type(value) is not FrozenJsonObject for value in self.evidence_cards):
1494
+ raise TypeError("evidence_cards must contain frozen objects")
1495
+ measurement = self.parent_measurement
1496
+ if measurement is not None:
1497
+ if type(measurement) is not ParentMeasurementBinding:
1498
+ raise TypeError("parent_measurement must be exact or None")
1499
+ measurement.__post_init__()
1500
+ occurrence = self.parent.occurrence
1501
+ operator_invocation_id = (
1502
+ None
1503
+ if occurrence.operator_invocation_id is None
1504
+ else occurrence.operator_invocation_id.value
1505
+ )
1506
+ if (
1507
+ measurement.candidate.candidate_id != self.parent.candidate_id.value
1508
+ or measurement.candidate.configuration_sha256
1509
+ != occurrence.configuration_hash
1510
+ or measurement.candidate.configuration_artifact_sha256
1511
+ != occurrence.configuration_artifact_hash
1512
+ or measurement.candidate.proposal_sequence
1513
+ != occurrence.proposal_sequence
1514
+ or measurement.candidate.operator_invocation_id
1515
+ != operator_invocation_id
1516
+ or measurement.projection.benchmark_sha256
1517
+ != self.variation.benchmark_sha256
1518
+ or measurement.projection.session_sha256
1519
+ != self.prepared.benchmark_session.session_sha256
1520
+ ):
1521
+ raise ValueError("parent measurement differs from campaign parent")
1522
+ if type(self.test_eligible_reflections) is not tuple:
1523
+ raise TypeError("test_eligible_reflections must be an exact tuple")
1524
+ observed_hashes: list[str] = []
1525
+ for item in self.test_eligible_reflections:
1526
+ if (
1527
+ type(item) is not tuple
1528
+ or len(item) != 2
1529
+ or type(item[0]) is not str
1530
+ or len(item[0]) != 64
1531
+ or type(item[1]) is not FrozenJsonObject
1532
+ ):
1533
+ raise TypeError(
1534
+ "test-eligible reflections must contain hash/object pairs"
1535
+ )
1536
+ observed_hashes.append(item[0])
1537
+ if tuple(observed_hashes) != tuple(sorted(set(observed_hashes))):
1538
+ raise ValueError("test-eligible reflection hashes must be canonical")
1539
+
1540
+
1541
+ @runtime_checkable
1542
+ class CampaignPortfolioWaveFactory(Protocol):
1543
+ """Turn trusted workload evidence into one executable portfolio wave."""
1544
+
1545
+ def build(
1546
+ self,
1547
+ context: CampaignPortfolioWaveContext,
1548
+ ) -> PortfolioVariationWaveRequest: ...
1549
+
1550
+
1551
+ @runtime_checkable
1552
+ class CampaignPortfolioWaveBatchFactory(Protocol):
1553
+ """Optionally construct one portfolio stage as an atomic lane cohort.
1554
+
1555
+ Batch construction is required when a workload-neutral policy must solve a
1556
+ constraint across lanes, such as assigning distinct compatible memory
1557
+ cards. The ordinary single-wave factory remains the default compatibility
1558
+ surface; runtimes opt into this protocol structurally through
1559
+ ``build_batch``.
1560
+ """
1561
+
1562
+ def build_batch(
1563
+ self,
1564
+ contexts: tuple[CampaignPortfolioWaveContext, ...],
1565
+ ) -> tuple[PortfolioVariationWaveRequest, ...]: ...
1566
+
1567
+
1568
+ @runtime_checkable
1569
+ class CampaignPortfolioContextEnricher(Protocol):
1570
+ """Retrieve parent/lane-local history immediately before wave building.
1571
+
1572
+ The input is the complete authenticated base context. Implementations may
1573
+ query contextual outcome memory, but return only a bounded frozen history
1574
+ payload. The runtime attaches it under a reserved key, preserving every
1575
+ workload- and core-authored base-context field exactly.
1576
+ """
1577
+
1578
+ def enrich(
1579
+ self,
1580
+ context: CampaignPortfolioWaveContext,
1581
+ ) -> FrozenJsonObject: ...
1582
+
1583
+
1584
+ @dataclass(frozen=True, slots=True)
1585
+ class CampaignPortfolioMemoryEstimandProjection:
1586
+ """Atomic replacement for the core-owned memory-estimand context pair.
1587
+
1588
+ The campaign runtime, rather than a workload wave factory, installs this
1589
+ projection into the trusted selector context. The digest is deliberately
1590
+ redundant: it binds a diagnostic stratum to the exact typed-JSON estimand
1591
+ object and prevents a projector from publishing a mismatched pair.
1592
+ """
1593
+
1594
+ estimand_context: FrozenJsonObject
1595
+ estimand_stratum_sha256: str
1596
+
1597
+ def __post_init__(self) -> None:
1598
+ if (
1599
+ type(self.estimand_context) is not FrozenJsonObject
1600
+ or freeze_json(self.estimand_context) is not self.estimand_context
1601
+ ):
1602
+ raise TypeError("estimand_context must be an exact frozen object")
1603
+ _require_sha256(
1604
+ self.estimand_stratum_sha256,
1605
+ "estimand_stratum_sha256",
1606
+ )
1607
+ if typed_json_sha256(self.estimand_context) != self.estimand_stratum_sha256:
1608
+ raise ValueError(
1609
+ "estimand_stratum_sha256 must identify the exact estimand context"
1610
+ )
1611
+
1612
+
1613
+ @runtime_checkable
1614
+ class CampaignPortfolioMemoryEstimandProjector(Protocol):
1615
+ """Project a prospective wave onto an authenticated memory estimand.
1616
+
1617
+ Returning ``None`` preserves the workload-authored selector context. A
1618
+ projection authorizes replacement of exactly the core-owned estimand
1619
+ object and matching stratum digest; every other context field is retained
1620
+ byte-for-byte by the campaign runtime.
1621
+ """
1622
+
1623
+ def project(
1624
+ self,
1625
+ context: CampaignPortfolioWaveContext,
1626
+ ) -> CampaignPortfolioMemoryEstimandProjection | None: ...
1627
+
1628
+
1629
+ @dataclass(frozen=True, slots=True)
1630
+ class CampaignPortfolioWavePreparationReceipt:
1631
+ """Content-free evidence that one trusted wave was ready for dispatch.
1632
+
1633
+ This receipt is emitted after the wave factory's output has passed the
1634
+ campaign trust-boundary validation and before any selector/provider work
1635
+ starts. It deliberately records prompt-adjacent objects only through
1636
+ their existing canonical hashes. ``card_records`` are the public
1637
+ :class:`PortfolioCard` audit records, which contain prompt-view hashes but
1638
+ never the prompt payload itself.
1639
+
1640
+ A failed stage is not a sealed scientific result. The receipt instead
1641
+ preserves the prospective treatment, projection, and lineage needed to
1642
+ audit why that unsealed stage would have dispatched the cards it did.
1643
+ """
1644
+
1645
+ campaign_preparation_sha256: str
1646
+ stage_request_sha256: str
1647
+ stage_preparation_sha256: str
1648
+ generation: int
1649
+ parent_slot: int
1650
+ parent_lane_id: str
1651
+ decision_slot_id: str
1652
+ parent_candidate_id: str
1653
+ parent_configuration_sha256: str
1654
+ parent_configuration_artifact_sha256: str
1655
+ finite_contract_identity_sha256: str
1656
+ selector_call_id: str
1657
+ selector_request_sha256: str
1658
+ workload_context_sha256: str
1659
+ pre_memory_projection_context_sha256: str
1660
+ selector_context_sha256: str
1661
+ evidence_card_snapshot_sha256: str
1662
+ selector_card_snapshot_sha256: str
1663
+ card_records: tuple[FrozenJsonObject, ...]
1664
+ card_reference_mapping: tuple[FrozenJsonObject, ...]
1665
+ test_eligible_reflection_receipts: tuple[FrozenJsonObject, ...]
1666
+ context_projection_identity: FrozenJsonObject
1667
+ memory_credit_identity: FrozenJsonObject | None
1668
+ receipt_sha256: str = field(init=False)
1669
+
1670
+ def __post_init__(self) -> None:
1671
+ for name in (
1672
+ "campaign_preparation_sha256",
1673
+ "stage_request_sha256",
1674
+ "stage_preparation_sha256",
1675
+ "parent_configuration_sha256",
1676
+ "parent_configuration_artifact_sha256",
1677
+ "finite_contract_identity_sha256",
1678
+ "selector_request_sha256",
1679
+ "workload_context_sha256",
1680
+ "pre_memory_projection_context_sha256",
1681
+ "selector_context_sha256",
1682
+ "evidence_card_snapshot_sha256",
1683
+ "selector_card_snapshot_sha256",
1684
+ ):
1685
+ _require_sha256(getattr(self, name), name)
1686
+ if type(self.generation) is not int or self.generation <= 0:
1687
+ raise ValueError("generation must be a positive exact integer")
1688
+ if type(self.parent_slot) is not int or self.parent_slot < 0:
1689
+ raise ValueError("parent_slot must be a non-negative exact integer")
1690
+ _require_role_token(self.parent_lane_id, "parent_lane_id")
1691
+ _require_role_token(self.decision_slot_id, "decision_slot_id")
1692
+ for name in ("parent_candidate_id", "selector_call_id"):
1693
+ value = getattr(self, name)
1694
+ if type(value) is not str or not value:
1695
+ raise ValueError(f"{name} must be a non-empty exact string")
1696
+ for name in (
1697
+ "card_records",
1698
+ "card_reference_mapping",
1699
+ "test_eligible_reflection_receipts",
1700
+ ):
1701
+ values = getattr(self, name)
1702
+ if type(values) is not tuple or any(
1703
+ type(value) is not FrozenJsonObject or freeze_json(value) is not value
1704
+ for value in values
1705
+ ):
1706
+ raise TypeError(f"{name} must contain exact frozen objects")
1707
+ if not self.card_records:
1708
+ raise ValueError("a prepared portfolio wave requires card records")
1709
+ if len(self.card_records) != len(self.card_reference_mapping):
1710
+ raise ValueError("card records and reference mapping differ")
1711
+ card_keys = tuple(
1712
+ thaw_json(value).get("card_key") for value in self.card_records
1713
+ )
1714
+ mapped_keys = tuple(
1715
+ thaw_json(value).get("card_key") for value in self.card_reference_mapping
1716
+ )
1717
+ if card_keys != mapped_keys or card_keys != tuple(sorted(set(card_keys))):
1718
+ raise ValueError("card records and references require canonical card keys")
1719
+ reflection_receipts = tuple(
1720
+ thaw_json(value).get("reflection_receipt_sha256")
1721
+ for value in self.test_eligible_reflection_receipts
1722
+ )
1723
+ if reflection_receipts != tuple(sorted(set(reflection_receipts))):
1724
+ raise ValueError("test-eligible reflection receipts must be canonical")
1725
+ for value in self.test_eligible_reflection_receipts:
1726
+ record = thaw_json(value)
1727
+ _require_sha256(
1728
+ record.get("reflection_receipt_sha256"),
1729
+ "reflection_receipt_sha256",
1730
+ )
1731
+ _require_sha256(
1732
+ record.get("reflection_result_sha256"),
1733
+ "reflection_result_sha256",
1734
+ )
1735
+ for name in ("context_projection_identity", "memory_credit_identity"):
1736
+ value = getattr(self, name)
1737
+ if value is not None and (
1738
+ type(value) is not FrozenJsonObject or freeze_json(value) is not value
1739
+ ):
1740
+ raise TypeError(f"{name} must be an exact frozen object or None")
1741
+ context_projection = thaw_json(self.context_projection_identity)
1742
+ if (
1743
+ context_projection.get("selector_context_sha256")
1744
+ != self.selector_context_sha256
1745
+ ):
1746
+ raise ValueError("context projection names a different selector context")
1747
+ object.__setattr__(
1748
+ self,
1749
+ "receipt_sha256",
1750
+ _preparation_sha256(
1751
+ _WAVE_PREPARATION_DOMAIN,
1752
+ _object(self._unsigned_record()),
1753
+ ),
1754
+ )
1755
+
1756
+ def _unsigned_record(self) -> dict[str, object]:
1757
+ return {
1758
+ "schema_version": 1,
1759
+ "campaign_preparation_sha256": self.campaign_preparation_sha256,
1760
+ "stage_request_sha256": self.stage_request_sha256,
1761
+ "stage_preparation_sha256": self.stage_preparation_sha256,
1762
+ "generation": self.generation,
1763
+ "parent_slot": self.parent_slot,
1764
+ "parent_lane_id": self.parent_lane_id,
1765
+ "decision_slot_id": self.decision_slot_id,
1766
+ "parent_candidate_id": self.parent_candidate_id,
1767
+ "parent_configuration_sha256": self.parent_configuration_sha256,
1768
+ "parent_configuration_artifact_sha256": (
1769
+ self.parent_configuration_artifact_sha256
1770
+ ),
1771
+ "finite_contract_identity_sha256": (self.finite_contract_identity_sha256),
1772
+ "selector_call_id": self.selector_call_id,
1773
+ "selector_request_sha256": self.selector_request_sha256,
1774
+ "workload_context_sha256": self.workload_context_sha256,
1775
+ "pre_memory_projection_context_sha256": (
1776
+ self.pre_memory_projection_context_sha256
1777
+ ),
1778
+ "selector_context_sha256": self.selector_context_sha256,
1779
+ "evidence_card_snapshot_sha256": self.evidence_card_snapshot_sha256,
1780
+ "selector_card_snapshot_sha256": self.selector_card_snapshot_sha256,
1781
+ "card_records": [thaw_json(value) for value in self.card_records],
1782
+ "card_reference_mapping": [
1783
+ thaw_json(value) for value in self.card_reference_mapping
1784
+ ],
1785
+ "test_eligible_reflection_receipts": [
1786
+ thaw_json(value) for value in self.test_eligible_reflection_receipts
1787
+ ],
1788
+ "context_projection_identity": thaw_json(self.context_projection_identity),
1789
+ "memory_credit_identity": (
1790
+ None
1791
+ if self.memory_credit_identity is None
1792
+ else thaw_json(self.memory_credit_identity)
1793
+ ),
1794
+ }
1795
+
1796
+ def to_record(self) -> dict[str, object]:
1797
+ self.__post_init__()
1798
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
1799
+
1800
+
1801
+ @runtime_checkable
1802
+ class CampaignPortfolioWavePreparationObserver(Protocol):
1803
+ """Durably record a validated prospective wave before dispatch.
1804
+
1805
+ Implementations must finish durable publication before returning. Raising
1806
+ aborts the stage before any selector call is submitted. The observer is
1807
+ intentionally separate from campaign stage receipts because a failed or
1808
+ cancelled stage never has a sealed :class:`CampaignStageReceipt`.
1809
+ """
1810
+
1811
+ def record_prepared_wave(
1812
+ self,
1813
+ receipt: CampaignPortfolioWavePreparationReceipt,
1814
+ ) -> None: ...
1815
+
1816
+
1817
+ def _validated_wave_preparation_receipt(
1818
+ *,
1819
+ build: CampaignPortfolioWaveContext,
1820
+ wave: PortfolioVariationWaveRequest,
1821
+ workload_context_sha256: str,
1822
+ pre_memory_projection_context_sha256: str,
1823
+ ) -> CampaignPortfolioWavePreparationReceipt:
1824
+ """Project an already validated wave into content-free audit evidence."""
1825
+
1826
+ request = wave.selection_request
1827
+ credit = wave.memory_credit
1828
+ matched = wave.matched_memory_control
1829
+ if credit is None and matched is None:
1830
+ context_values = dict(request.context.items)
1831
+ context_projection = (
1832
+ PortfolioMemoryContextProjectionBinding.from_selector_context(
1833
+ request.context
1834
+ )
1835
+ if MEMORY_ESTIMAND_CONTEXT_KEY in context_values
1836
+ else PortfolioMemoryContextProjectionBinding.exact_identity(
1837
+ request.context_sha256
1838
+ )
1839
+ )
1840
+ memory_credit_identity = None
1841
+ elif credit is not None:
1842
+ context_projection = credit.resolve_context_projection(request.context)
1843
+ admission = credit.quarantine_admission
1844
+ memory_credit_identity = _object(
1845
+ {
1846
+ "schema_version": 1,
1847
+ "credit_unit_id": credit.credit_unit_id.value,
1848
+ "treatment_binding_sha256": credit.treatment_binding_sha256,
1849
+ "selection_decision_sha256": (
1850
+ credit.assignment.selection_decision_sha256
1851
+ ),
1852
+ "assignment_sha256": credit.assignment.assignment_sha256,
1853
+ "score_snapshot_sha256": credit.score_snapshot.snapshot_sha256,
1854
+ "aggregation_binding_sha256": credit.aggregation.binding_sha256,
1855
+ "card_source_registry_sha256": (credit.card_source_registry_sha256),
1856
+ "context_projection_binding_sha256": (
1857
+ context_projection.binding_sha256
1858
+ ),
1859
+ "quarantine_admission": (
1860
+ None
1861
+ if admission is None
1862
+ else {
1863
+ "receipt_sha256": admission.receipt_sha256,
1864
+ "source_admission_request_sha256": (
1865
+ admission.source_admission_request_sha256
1866
+ ),
1867
+ "references": [
1868
+ {
1869
+ "insight_id": value.insight_id.value,
1870
+ "version": value.version,
1871
+ }
1872
+ for value in admission.references
1873
+ ],
1874
+ }
1875
+ ),
1876
+ "quarantine_admission_subset_authorization_sha256": (
1877
+ credit.quarantine_admission_subset_authorization_sha256
1878
+ ),
1879
+ }
1880
+ )
1881
+ else:
1882
+ assert matched is not None
1883
+ context_projection = matched.context_projection
1884
+ context_projection.replay(request.context)
1885
+ parent_lane = build.parent_lane
1886
+ if parent_lane is None:
1887
+ raise ValueError("matched memory arm requires a stable campaign lane")
1888
+ if (
1889
+ matched.assignment.unit.generation != wave.generation
1890
+ or matched.assignment.unit.lane_id != parent_lane.lane_id
1891
+ ):
1892
+ raise ValueError("matched memory assignment differs from the campaign lane")
1893
+ memory_credit_identity = _object(
1894
+ {
1895
+ "schema_version": 2,
1896
+ "evidence_kind": "randomized_active_neutral_arm",
1897
+ "plan": matched.plan.to_record(),
1898
+ "assignment": matched.assignment.to_record(),
1899
+ "arm_view": matched.arm_view.to_record(),
1900
+ "aggregation": {
1901
+ **matched.aggregation.to_record(),
1902
+ "binding_sha256": matched.aggregation.binding_sha256,
1903
+ },
1904
+ "context_projection_binding_sha256": (
1905
+ context_projection.binding_sha256
1906
+ ),
1907
+ "card_vs_neutral_effect_identified": False,
1908
+ "online_score_update_allowed": False,
1909
+ }
1910
+ )
1911
+ context_projection_record: dict[str, object] = {
1912
+ **context_projection.to_record(),
1913
+ "binding_sha256": context_projection.binding_sha256,
1914
+ }
1915
+ selector_context_values = dict(request.context.items)
1916
+ selector_context_extension = resolve_campaign_selector_context_extension(
1917
+ trusted_context=build.evidence_context,
1918
+ selector_context=request.context,
1919
+ )
1920
+ if selector_context_extension is not None:
1921
+ context_projection_record["selector_context_extension"] = {
1922
+ "reserved_key": CAMPAIGN_SELECTOR_CONTEXT_EXTENSION_KEY,
1923
+ **selector_context_extension.to_binding_record(),
1924
+ }
1925
+ archive_context = selector_context_values.get(CAMPAIGN_ARCHIVE_CONTEXT_KEY)
1926
+ if archive_context is not None:
1927
+ if type(archive_context) is not FrozenJsonObject:
1928
+ raise TypeError("reserved campaign archive context must be a frozen object")
1929
+ context_projection_record["archive_context_projection"] = {
1930
+ "reserved_key": CAMPAIGN_ARCHIVE_CONTEXT_KEY,
1931
+ "projection_sha256": typed_json_sha256(archive_context),
1932
+ }
1933
+ frontier_target = selector_context_values.get(CAMPAIGN_FRONTIER_TARGET_KEY)
1934
+ if frontier_target is not None:
1935
+ if type(frontier_target) is not FrozenJsonObject:
1936
+ raise TypeError("reserved campaign frontier target must be frozen")
1937
+ if build.frontier_target is None:
1938
+ raise ValueError("selector context has an unauthenticated frontier target")
1939
+ if thaw_json(frontier_target) != build.frontier_target.to_record():
1940
+ raise ValueError("selector frontier target differs from its receipt")
1941
+ context_projection_record["frontier_target_projection"] = {
1942
+ "reserved_key": CAMPAIGN_FRONTIER_TARGET_KEY,
1943
+ "target_sha256": build.frontier_target.target_sha256,
1944
+ "projection_sha256": typed_json_sha256(frontier_target),
1945
+ }
1946
+ context_projection_identity = _object(context_projection_record)
1947
+ card_records = tuple(_object(card.to_record()) for card in request.cards)
1948
+ card_reference_mapping = tuple(
1949
+ _object(
1950
+ {
1951
+ "card_key": card.card_key,
1952
+ "reference": {
1953
+ "insight_id": card.reference.insight_id.value,
1954
+ "version": card.reference.version,
1955
+ },
1956
+ "source_binding_sha256": (
1957
+ None
1958
+ if card.source_binding is None
1959
+ else card.source_binding.binding_sha256
1960
+ ),
1961
+ "derived_view_receipt_sha256": (
1962
+ None
1963
+ if card.derived_view_receipt is None
1964
+ else card.derived_view_receipt.receipt_sha256
1965
+ ),
1966
+ }
1967
+ )
1968
+ for card in request.cards
1969
+ )
1970
+ test_eligible_reflections = tuple(
1971
+ _object(
1972
+ {
1973
+ "reflection_receipt_sha256": receipt_sha256,
1974
+ "reflection_result_sha256": typed_json_sha256(result),
1975
+ }
1976
+ )
1977
+ for receipt_sha256, result in build.test_eligible_reflections
1978
+ )
1979
+ evidence_card_snapshot_sha256 = typed_json_sha256(
1980
+ _object(
1981
+ {
1982
+ "schema_version": 1,
1983
+ "cards": [thaw_json(value) for value in build.evidence_cards],
1984
+ }
1985
+ )
1986
+ )
1987
+ parent_lane = build.parent_lane
1988
+ decision_slot = build.decision_slot
1989
+ assert parent_lane is not None
1990
+ assert decision_slot is not None
1991
+ occurrence = build.parent.occurrence
1992
+ return CampaignPortfolioWavePreparationReceipt(
1993
+ campaign_preparation_sha256=build.prepared.preparation_sha256,
1994
+ stage_request_sha256=build.stage_request.request_sha256,
1995
+ stage_preparation_sha256=build.stage_request.preparation_sha256,
1996
+ generation=wave.generation,
1997
+ parent_slot=build.parent_slot,
1998
+ parent_lane_id=parent_lane.lane_id,
1999
+ decision_slot_id=decision_slot.slot_id,
2000
+ parent_candidate_id=build.parent.candidate_id.value,
2001
+ parent_configuration_sha256=occurrence.configuration_hash,
2002
+ parent_configuration_artifact_sha256=(occurrence.configuration_artifact_hash),
2003
+ finite_contract_identity_sha256=(
2004
+ request.finite_variation_contract.identity_sha256
2005
+ ),
2006
+ selector_call_id=request.call_id.value,
2007
+ selector_request_sha256=request.request_sha256,
2008
+ workload_context_sha256=workload_context_sha256,
2009
+ pre_memory_projection_context_sha256=(pre_memory_projection_context_sha256),
2010
+ selector_context_sha256=request.context_sha256,
2011
+ evidence_card_snapshot_sha256=evidence_card_snapshot_sha256,
2012
+ selector_card_snapshot_sha256=request.card_snapshot_sha256,
2013
+ card_records=card_records,
2014
+ card_reference_mapping=card_reference_mapping,
2015
+ test_eligible_reflection_receipts=test_eligible_reflections,
2016
+ context_projection_identity=context_projection_identity,
2017
+ memory_credit_identity=memory_credit_identity,
2018
+ )
2019
+
2020
+
2021
+ @dataclass(frozen=True, slots=True)
2022
+ class CampaignPortfolioOutcomePreparation:
2023
+ """Pure workload-memory projection awaiting a campaign commit barrier."""
2024
+
2025
+ request_sha256: str
2026
+ generation: int
2027
+ wave_request_sha256s: tuple[str, ...]
2028
+ result_receipt_sha256s: tuple[str, ...]
2029
+ prior_memory_sha256: str
2030
+ updated_memory: FrozenJsonObject
2031
+ evidence: FrozenJsonObject
2032
+ preparation_sha256: str = field(init=False)
2033
+
2034
+ def __post_init__(self) -> None:
2035
+ _require_sha256(self.request_sha256, "request_sha256")
2036
+ if type(self.generation) is not int or self.generation <= 0:
2037
+ raise ValueError("generation must be a positive exact integer")
2038
+ for name in ("wave_request_sha256s", "result_receipt_sha256s"):
2039
+ values = getattr(self, name)
2040
+ if type(values) is not tuple or not values:
2041
+ raise ValueError(f"{name} must be a non-empty exact tuple")
2042
+ for value in values:
2043
+ _require_sha256(value, name)
2044
+ if len(self.wave_request_sha256s) != len(self.result_receipt_sha256s):
2045
+ raise ValueError("outcome preparation waves and results differ")
2046
+ _require_sha256(self.prior_memory_sha256, "prior_memory_sha256")
2047
+ for name in ("updated_memory", "evidence"):
2048
+ value = getattr(self, name)
2049
+ if type(value) is not FrozenJsonObject or freeze_json(value) is not value:
2050
+ raise TypeError(f"{name} must be an exact frozen object")
2051
+ object.__setattr__(
2052
+ self,
2053
+ "preparation_sha256",
2054
+ _preparation_sha256(
2055
+ _OUTCOME_PREPARATION_DOMAIN,
2056
+ _object(
2057
+ {
2058
+ "schema_version": 1,
2059
+ "request_sha256": self.request_sha256,
2060
+ "generation": self.generation,
2061
+ "wave_request_sha256s": list(self.wave_request_sha256s),
2062
+ "result_receipt_sha256s": list(self.result_receipt_sha256s),
2063
+ "prior_memory_sha256": self.prior_memory_sha256,
2064
+ "updated_memory_sha256": typed_json_sha256(self.updated_memory),
2065
+ "evidence_sha256": typed_json_sha256(self.evidence),
2066
+ }
2067
+ ),
2068
+ ),
2069
+ )
2070
+
2071
+ def to_record(self) -> dict[str, object]:
2072
+ self.__post_init__()
2073
+ return {
2074
+ "schema_version": 1,
2075
+ "request_sha256": self.request_sha256,
2076
+ "generation": self.generation,
2077
+ "wave_request_sha256s": list(self.wave_request_sha256s),
2078
+ "result_receipt_sha256s": list(self.result_receipt_sha256s),
2079
+ "prior_memory_sha256": self.prior_memory_sha256,
2080
+ "updated_memory_sha256": typed_json_sha256(self.updated_memory),
2081
+ "evidence": thaw_json(self.evidence),
2082
+ "preparation_sha256": self.preparation_sha256,
2083
+ }
2084
+
2085
+
2086
+ @dataclass(frozen=True, slots=True)
2087
+ class CampaignPortfolioLearningPreparation:
2088
+ """Pure lifecycle adjudication awaiting a campaign commit barrier."""
2089
+
2090
+ request_sha256: str
2091
+ generation: int
2092
+ wave_request_sha256s: tuple[str, ...]
2093
+ result_receipt_sha256s: tuple[str, ...]
2094
+ memory_credit_preparation_sha256: str
2095
+ evidence: FrozenJsonObject
2096
+ preparation_sha256: str = field(init=False)
2097
+
2098
+ def __post_init__(self) -> None:
2099
+ _require_sha256(self.request_sha256, "request_sha256")
2100
+ if type(self.generation) is not int or self.generation <= 0:
2101
+ raise ValueError("generation must be a positive exact integer")
2102
+ for name in ("wave_request_sha256s", "result_receipt_sha256s"):
2103
+ values = getattr(self, name)
2104
+ if type(values) is not tuple or not values:
2105
+ raise ValueError(f"{name} must be a non-empty exact tuple")
2106
+ for value in values:
2107
+ _require_sha256(value, name)
2108
+ if len(self.wave_request_sha256s) != len(self.result_receipt_sha256s):
2109
+ raise ValueError("learning preparation waves and results differ")
2110
+ _require_sha256(
2111
+ self.memory_credit_preparation_sha256,
2112
+ "memory_credit_preparation_sha256",
2113
+ )
2114
+ if (
2115
+ type(self.evidence) is not FrozenJsonObject
2116
+ or freeze_json(self.evidence) is not self.evidence
2117
+ ):
2118
+ raise TypeError("evidence must be an exact frozen object")
2119
+ object.__setattr__(
2120
+ self,
2121
+ "preparation_sha256",
2122
+ _preparation_sha256(
2123
+ _LEARNING_PREPARATION_DOMAIN,
2124
+ _object(
2125
+ {
2126
+ "schema_version": 1,
2127
+ "request_sha256": self.request_sha256,
2128
+ "generation": self.generation,
2129
+ "wave_request_sha256s": list(self.wave_request_sha256s),
2130
+ "result_receipt_sha256s": list(self.result_receipt_sha256s),
2131
+ "memory_credit_preparation_sha256": (
2132
+ self.memory_credit_preparation_sha256
2133
+ ),
2134
+ "evidence_sha256": typed_json_sha256(self.evidence),
2135
+ }
2136
+ ),
2137
+ ),
2138
+ )
2139
+
2140
+ def to_record(self) -> dict[str, object]:
2141
+ self.__post_init__()
2142
+ return {
2143
+ "schema_version": 1,
2144
+ "request_sha256": self.request_sha256,
2145
+ "generation": self.generation,
2146
+ "wave_request_sha256s": list(self.wave_request_sha256s),
2147
+ "result_receipt_sha256s": list(self.result_receipt_sha256s),
2148
+ "memory_credit_preparation_sha256": (self.memory_credit_preparation_sha256),
2149
+ "evidence": thaw_json(self.evidence),
2150
+ "preparation_sha256": self.preparation_sha256,
2151
+ }
2152
+
2153
+
2154
+ @runtime_checkable
2155
+ class CampaignSelectorRequestPromptRenderer(Protocol):
2156
+ """Render the exact prompt sent by an opt-in campaign selector.
2157
+
2158
+ The default campaign path keeps using the stable direct-selector renderer.
2159
+ Selectors with a different wire contract must inject an implementation
2160
+ backed by the same authenticated pre-call inputs used for provider
2161
+ execution; reconstructing a plausible prompt after the call is not audit
2162
+ evidence.
2163
+ """
2164
+
2165
+ def render(self, request: PortfolioSelectionRequest) -> str: ...
2166
+
2167
+
2168
+ @dataclass(frozen=True, slots=True)
2169
+ class CampaignIdentifiableReflectionEvidenceQuery:
2170
+ """Content-minimized request for one committed mutation-evidence cohort.
2171
+
2172
+ The query deliberately carries the source *receipt identity*, never the
2173
+ source-stage payload or recombination results. Its sealed cutoff is the
2174
+ portfolio generation that produced the direct mutations; the later
2175
+ recombination generation only supplies the scheduling boundary.
2176
+ """
2177
+
2178
+ reflection_request_sha256: str
2179
+ preparation_sha256: str
2180
+ runtime_start_receipt_sha256: str
2181
+ campaign_sha256: str
2182
+ workload_instance_sha256: str
2183
+ evaluator_contract_sha256: str
2184
+ wave: CampaignReflectionWave
2185
+ source_stage_receipt_sha256: str
2186
+ source_portfolio_generation: int
2187
+ prior_cutoff_event_index_exclusive: int
2188
+ sealed_cutoff_event_index_inclusive: int
2189
+ query_sha256: str = field(init=False)
2190
+
2191
+ def __post_init__(self) -> None:
2192
+ for name in (
2193
+ "reflection_request_sha256",
2194
+ "preparation_sha256",
2195
+ "runtime_start_receipt_sha256",
2196
+ "campaign_sha256",
2197
+ "workload_instance_sha256",
2198
+ "evaluator_contract_sha256",
2199
+ "source_stage_receipt_sha256",
2200
+ ):
2201
+ _require_sha256(getattr(self, name), name)
2202
+ if type(self.wave) is not CampaignReflectionWave:
2203
+ raise TypeError("wave must be an exact CampaignReflectionWave")
2204
+ CampaignReflectionWave.__post_init__(self.wave)
2205
+ if (
2206
+ type(self.source_portfolio_generation) is not int
2207
+ or self.source_portfolio_generation <= 0
2208
+ or self.source_portfolio_generation >= self.wave.source_generation
2209
+ ):
2210
+ raise ValueError(
2211
+ "source portfolio generation must precede the reflection source"
2212
+ )
2213
+ for name in (
2214
+ "prior_cutoff_event_index_exclusive",
2215
+ "sealed_cutoff_event_index_inclusive",
2216
+ ):
2217
+ value = getattr(self, name)
2218
+ if type(value) is not int or value < 0:
2219
+ raise ValueError(f"{name} must be a non-negative exact integer")
2220
+ if self.sealed_cutoff_event_index_inclusive != self.source_portfolio_generation:
2221
+ raise ValueError(
2222
+ "sealed evidence cutoff must equal the source portfolio generation"
2223
+ )
2224
+ if (
2225
+ self.prior_cutoff_event_index_exclusive
2226
+ >= self.sealed_cutoff_event_index_inclusive
2227
+ ):
2228
+ raise ValueError("reflection evidence cutoff must strictly advance")
2229
+ object.__setattr__(
2230
+ self,
2231
+ "query_sha256",
2232
+ _preparation_sha256(
2233
+ _IDENTIFIABLE_REFLECTION_QUERY_DOMAIN,
2234
+ _object(self._unsigned_record()),
2235
+ ),
2236
+ )
2237
+
2238
+ def _unsigned_record(self) -> dict[str, object]:
2239
+ return {
2240
+ "schema_version": 1,
2241
+ "reflection_request_sha256": self.reflection_request_sha256,
2242
+ "preparation_sha256": self.preparation_sha256,
2243
+ "runtime_start_receipt_sha256": self.runtime_start_receipt_sha256,
2244
+ "campaign_sha256": self.campaign_sha256,
2245
+ "workload_instance_sha256": self.workload_instance_sha256,
2246
+ "evaluator_contract_sha256": self.evaluator_contract_sha256,
2247
+ "wave": self.wave.to_record(),
2248
+ "source_stage_receipt_sha256": self.source_stage_receipt_sha256,
2249
+ "source_portfolio_generation": self.source_portfolio_generation,
2250
+ "prior_cutoff_event_index_exclusive": (
2251
+ self.prior_cutoff_event_index_exclusive
2252
+ ),
2253
+ "sealed_cutoff_event_index_inclusive": (
2254
+ self.sealed_cutoff_event_index_inclusive
2255
+ ),
2256
+ "source_stage_payload_exposed": False,
2257
+ "recombination_results_exposed": False,
2258
+ }
2259
+
2260
+ def to_record(self) -> dict[str, object]:
2261
+ self.__post_init__()
2262
+ return {**self._unsigned_record(), "query_sha256": self.query_sha256}
2263
+
2264
+
2265
+ @dataclass(frozen=True, slots=True)
2266
+ class CampaignIdentifiableReflectionEvidenceProjection:
2267
+ """Evidence-source response bound to one exact committed registry cutoff."""
2268
+
2269
+ query_sha256: str
2270
+ registry_snapshot_sha256: str
2271
+ registry_captured_through_event_index: int
2272
+ evidence: IdentifiableReflectionEvidenceSnapshot
2273
+ projection_sha256: str = field(init=False)
2274
+
2275
+ def __post_init__(self) -> None:
2276
+ _require_sha256(self.query_sha256, "query_sha256")
2277
+ _require_sha256(self.registry_snapshot_sha256, "registry_snapshot_sha256")
2278
+ if (
2279
+ type(self.registry_captured_through_event_index) is not int
2280
+ or self.registry_captured_through_event_index <= 0
2281
+ ):
2282
+ raise ValueError("registry_captured_through_event_index must be positive")
2283
+ if type(self.evidence) is not IdentifiableReflectionEvidenceSnapshot:
2284
+ raise TypeError(
2285
+ "evidence must be an exact IdentifiableReflectionEvidenceSnapshot"
2286
+ )
2287
+ IdentifiableReflectionEvidenceSnapshot.__post_init__(self.evidence)
2288
+ if (
2289
+ self.evidence.sealed_cutoff_event_index_inclusive
2290
+ != self.registry_captured_through_event_index
2291
+ ):
2292
+ raise ValueError("evidence cutoff differs from the registry snapshot")
2293
+ object.__setattr__(
2294
+ self,
2295
+ "projection_sha256",
2296
+ _preparation_sha256(
2297
+ _IDENTIFIABLE_REFLECTION_SOURCE_DOMAIN,
2298
+ _object(self._unsigned_record()),
2299
+ ),
2300
+ )
2301
+
2302
+ def _unsigned_record(self) -> dict[str, object]:
2303
+ return {
2304
+ "schema_version": 1,
2305
+ "query_sha256": self.query_sha256,
2306
+ "registry_snapshot_sha256": self.registry_snapshot_sha256,
2307
+ "registry_captured_through_event_index": (
2308
+ self.registry_captured_through_event_index
2309
+ ),
2310
+ "evidence_snapshot_sha256": self.evidence.snapshot_sha256,
2311
+ }
2312
+
2313
+ def to_record(self) -> dict[str, object]:
2314
+ self.__post_init__()
2315
+ return {
2316
+ **self._unsigned_record(),
2317
+ "evidence": self.evidence.to_record(),
2318
+ "projection_sha256": self.projection_sha256,
2319
+ }
2320
+
2321
+
2322
+ @dataclass(frozen=True, slots=True)
2323
+ class CampaignIdentifiableReflectionInput:
2324
+ """Only information an identifiable production reflection may consume."""
2325
+
2326
+ query: CampaignIdentifiableReflectionEvidenceQuery
2327
+ source: CampaignIdentifiableReflectionEvidenceProjection
2328
+ input_sha256: str = field(init=False)
2329
+
2330
+ def __post_init__(self) -> None:
2331
+ if type(self.query) is not CampaignIdentifiableReflectionEvidenceQuery:
2332
+ raise TypeError("query must be exact")
2333
+ CampaignIdentifiableReflectionEvidenceQuery.__post_init__(self.query)
2334
+ if type(self.source) is not CampaignIdentifiableReflectionEvidenceProjection:
2335
+ raise TypeError("source must be exact")
2336
+ CampaignIdentifiableReflectionEvidenceProjection.__post_init__(self.source)
2337
+ evidence = self.source.evidence
2338
+ if self.source.query_sha256 != self.query.query_sha256:
2339
+ raise ValueError("evidence source belongs to a foreign reflection query")
2340
+ if (
2341
+ evidence.prior_cutoff_event_index_exclusive
2342
+ != self.query.prior_cutoff_event_index_exclusive
2343
+ or evidence.sealed_cutoff_event_index_inclusive
2344
+ != self.query.sealed_cutoff_event_index_inclusive
2345
+ ):
2346
+ raise ValueError("evidence snapshot escapes the requested cutoffs")
2347
+ if (
2348
+ evidence.campaign_sha256 != self.query.campaign_sha256
2349
+ or evidence.workload_instance_sha256 != self.query.workload_instance_sha256
2350
+ or evidence.evaluator_contract_sha256
2351
+ != self.query.evaluator_contract_sha256
2352
+ ):
2353
+ raise ValueError("evidence snapshot escapes the requested scope")
2354
+ object.__setattr__(
2355
+ self,
2356
+ "input_sha256",
2357
+ _preparation_sha256(
2358
+ _IDENTIFIABLE_REFLECTION_INPUT_DOMAIN,
2359
+ _object(self._unsigned_record()),
2360
+ ),
2361
+ )
2362
+
2363
+ @property
2364
+ def evidence(self) -> IdentifiableReflectionEvidenceSnapshot:
2365
+ return self.source.evidence
2366
+
2367
+ def _unsigned_record(self) -> dict[str, object]:
2368
+ return {
2369
+ "schema_version": 1,
2370
+ "query_sha256": self.query.query_sha256,
2371
+ "source_projection_sha256": self.source.projection_sha256,
2372
+ "evidence_snapshot_sha256": self.source.evidence.snapshot_sha256,
2373
+ "source_stage_payload_exposed": False,
2374
+ "recombination_results_exposed": False,
2375
+ }
2376
+
2377
+ def to_record(self) -> dict[str, object]:
2378
+ self.__post_init__()
2379
+ return {
2380
+ **self._unsigned_record(),
2381
+ "query": self.query.to_record(),
2382
+ "source": self.source.to_record(),
2383
+ "input_sha256": self.input_sha256,
2384
+ }
2385
+
2386
+
2387
+ @runtime_checkable
2388
+ class CampaignIdentifiableReflectionEvidenceSource(Protocol):
2389
+ """Project a committed registry cohort without receiving stage payloads."""
2390
+
2391
+ campaign_sha256: str
2392
+ workload_instance_sha256: str
2393
+ evaluator_contract_sha256: str
2394
+
2395
+ def project(
2396
+ self,
2397
+ query: CampaignIdentifiableReflectionEvidenceQuery,
2398
+ ) -> CampaignIdentifiableReflectionEvidenceProjection: ...
2399
+
2400
+
2401
+ @runtime_checkable
2402
+ class CampaignReflectionFalsificationSource(Protocol):
2403
+ """Return feedback committed and visible at one reflection cutoff."""
2404
+
2405
+ def available(
2406
+ self,
2407
+ query: CampaignIdentifiableReflectionEvidenceQuery,
2408
+ ) -> tuple[ReflectionFalsificationFeedback, ...]: ...
2409
+
2410
+
2411
+ @dataclass(frozen=True, slots=True)
2412
+ class CommittedRegistryIdentifiableReflectionEvidenceSource:
2413
+ """Default adapter over an append-only committed campaign registry."""
2414
+
2415
+ registry: CampaignEvidenceRegistry
2416
+ campaign_sha256: str
2417
+ workload_instance_sha256: str
2418
+ evaluator_contract_sha256: str
2419
+ prior_falsifications: tuple[ReflectionFalsificationFeedback, ...] = ()
2420
+ falsification_source: CampaignReflectionFalsificationSource | None = None
2421
+
2422
+ def __post_init__(self) -> None:
2423
+ if type(self.registry) is not CampaignEvidenceRegistry:
2424
+ raise TypeError("registry must be an exact CampaignEvidenceRegistry")
2425
+ for name in (
2426
+ "campaign_sha256",
2427
+ "workload_instance_sha256",
2428
+ "evaluator_contract_sha256",
2429
+ ):
2430
+ _require_sha256(getattr(self, name), name)
2431
+ if type(self.prior_falsifications) is not tuple or any(
2432
+ type(value) is not ReflectionFalsificationFeedback
2433
+ for value in self.prior_falsifications
2434
+ ):
2435
+ raise TypeError("prior_falsifications must contain exact feedback")
2436
+ for value in self.prior_falsifications:
2437
+ ReflectionFalsificationFeedback.__post_init__(value)
2438
+ if self.falsification_source is not None and not isinstance(
2439
+ self.falsification_source,
2440
+ CampaignReflectionFalsificationSource,
2441
+ ):
2442
+ raise TypeError(
2443
+ "falsification_source must implement its narrow runtime port"
2444
+ )
2445
+ if self.prior_falsifications and self.falsification_source is not None:
2446
+ raise ValueError(
2447
+ "static and cutoff-aware falsification sources are mutually exclusive"
2448
+ )
2449
+
2450
+ def project(
2451
+ self,
2452
+ query: CampaignIdentifiableReflectionEvidenceQuery,
2453
+ ) -> CampaignIdentifiableReflectionEvidenceProjection:
2454
+ self.__post_init__()
2455
+ if type(query) is not CampaignIdentifiableReflectionEvidenceQuery:
2456
+ raise TypeError("query must be exact")
2457
+ CampaignIdentifiableReflectionEvidenceQuery.__post_init__(query)
2458
+ if (
2459
+ query.campaign_sha256 != self.campaign_sha256
2460
+ or query.workload_instance_sha256 != self.workload_instance_sha256
2461
+ or query.evaluator_contract_sha256 != self.evaluator_contract_sha256
2462
+ ):
2463
+ raise ValueError("reflection query names a foreign evidence scope")
2464
+ cutoff = query.sealed_cutoff_event_index_inclusive
2465
+ if self.registry.captured_through_event_index < cutoff:
2466
+ raise RuntimeError("committed evidence registry has not reached the cutoff")
2467
+ committed = tuple(
2468
+ value for value in self.registry.observations if value.event_index <= cutoff
2469
+ )
2470
+ registry_snapshot = GlobalEvidenceRegistrySnapshot.seal(
2471
+ captured_through_event_index=cutoff,
2472
+ observations=committed,
2473
+ )
2474
+ dynamic_source = self.falsification_source
2475
+ if dynamic_source is None:
2476
+ candidates = self.prior_falsifications
2477
+ else:
2478
+ candidates = dynamic_source.available(query)
2479
+ if type(candidates) is not tuple or any(
2480
+ type(value) is not ReflectionFalsificationFeedback
2481
+ for value in candidates
2482
+ ):
2483
+ raise TypeError(
2484
+ "falsification source must return exact feedback values"
2485
+ )
2486
+ for value in candidates:
2487
+ ReflectionFalsificationFeedback.__post_init__(value)
2488
+ if tuple(value.feedback_sha256 for value in candidates) != tuple(
2489
+ sorted({value.feedback_sha256 for value in candidates})
2490
+ ):
2491
+ raise ValueError(
2492
+ "falsification source must return canonical unique feedback"
2493
+ )
2494
+ applicable_falsifications = tuple(
2495
+ value
2496
+ for value in candidates
2497
+ if value.available_event_index <= cutoff
2498
+ and self.workload_instance_sha256
2499
+ in value.applicable_workload_instance_sha256s
2500
+ and value.evaluator_contract_sha256 == self.evaluator_contract_sha256
2501
+ and (
2502
+ not value.applicable_campaign_sha256s
2503
+ or self.campaign_sha256 in value.applicable_campaign_sha256s
2504
+ )
2505
+ )
2506
+ if dynamic_source is not None and applicable_falsifications != candidates:
2507
+ raise ValueError(
2508
+ "cutoff-aware falsification source returned foreign or future feedback"
2509
+ )
2510
+ evidence = project_identifiable_reflection_evidence(
2511
+ registry_snapshot.observations,
2512
+ campaign_sha256=self.campaign_sha256,
2513
+ workload_instance_sha256=self.workload_instance_sha256,
2514
+ evaluator_contract_sha256=self.evaluator_contract_sha256,
2515
+ prior_cutoff_event_index_exclusive=(
2516
+ query.prior_cutoff_event_index_exclusive
2517
+ ),
2518
+ sealed_cutoff_event_index_inclusive=cutoff,
2519
+ prior_falsifications=applicable_falsifications,
2520
+ )
2521
+ return CampaignIdentifiableReflectionEvidenceProjection(
2522
+ query_sha256=query.query_sha256,
2523
+ registry_snapshot_sha256=registry_snapshot.snapshot_sha256,
2524
+ registry_captured_through_event_index=cutoff,
2525
+ evidence=evidence,
2526
+ )
2527
+
2528
+
2529
+ @runtime_checkable
2530
+ class CampaignReflectionExecutor(Protocol):
2531
+ """Execute reflection from sealed direct-mutation evidence only."""
2532
+
2533
+ async def reflect(
2534
+ self,
2535
+ reflection_input: CampaignIdentifiableReflectionInput,
2536
+ ) -> FrozenJsonObject: ...
2537
+
2538
+
2539
+ @runtime_checkable
2540
+ class CampaignLegacyRecombinationReflectionExecutor(Protocol):
2541
+ """Deprecated compatibility port exposing non-identifiable recombinations."""
2542
+
2543
+ async def reflect(
2544
+ self,
2545
+ request: CampaignReflectionRequest,
2546
+ source_results: tuple[PortfolioRecombinationWaveResult, ...],
2547
+ ) -> FrozenJsonObject: ...
2548
+
2549
+
2550
+ @runtime_checkable
2551
+ class CampaignPortfolioOutcomeUpdater(Protocol):
2552
+ """Prepare then atomically publish workload-visible outcome state.
2553
+
2554
+ Exact causal credit inside :class:`InsightMemoryBank` remains owned by the
2555
+ wave's ``PortfolioMemoryCreditPlan``. ``prepare_update`` must not publish
2556
+ any ledger or workload state. ``commit_update`` is a synchronous, no-I/O,
2557
+ already-validated publication and must not fail for its own preparation.
2558
+ """
2559
+
2560
+ async def prepare_update(
2561
+ self,
2562
+ request: CampaignStageRequest,
2563
+ waves: tuple[PortfolioVariationWaveRequest, ...],
2564
+ results: tuple[PortfolioVariationWaveResult, ...],
2565
+ prior_memory: FrozenJsonObject,
2566
+ ) -> CampaignPortfolioOutcomePreparation: ...
2567
+
2568
+ def commit_update(
2569
+ self,
2570
+ preparation: CampaignPortfolioOutcomePreparation,
2571
+ ) -> None: ...
2572
+
2573
+ def abort_update(
2574
+ self,
2575
+ preparation: CampaignPortfolioOutcomePreparation,
2576
+ ) -> None: ...
2577
+
2578
+
2579
+ @runtime_checkable
2580
+ class CampaignLearningLifecyclePort(Protocol):
2581
+ """Join quarantined reflections to later diagnostic campaign barriers.
2582
+
2583
+ This is an observation/lifecycle seam, not a workload or provider API. It
2584
+ may stage exact reflection cards, admit them for controlled testing, and
2585
+ adjudicate them after a complete memory-credit barrier. Every method
2586
+ returns frozen evidence for the campaign trace.
2587
+ """
2588
+
2589
+ def reflection_completed(
2590
+ self,
2591
+ request: CampaignReflectionRequest,
2592
+ receipt: CampaignReflectionReceipt,
2593
+ result: FrozenJsonObject,
2594
+ ) -> FrozenJsonObject: ...
2595
+
2596
+ def reflections_admitted(
2597
+ self,
2598
+ request: CampaignReflectionTestAdmissionRequest,
2599
+ contents: tuple[tuple[CampaignReflectionReceipt, FrozenJsonObject], ...],
2600
+ ) -> FrozenJsonObject: ...
2601
+
2602
+ async def prepare_portfolio_generation_close(
2603
+ self,
2604
+ request: CampaignStageRequest,
2605
+ waves: tuple[PortfolioVariationWaveRequest, ...],
2606
+ results: tuple[PortfolioVariationWaveResult, ...],
2607
+ memory_credit_preparation: PortfolioMemoryCreditBatchPreparation,
2608
+ ) -> CampaignPortfolioLearningPreparation: ...
2609
+
2610
+ def commit_portfolio_generation_close(
2611
+ self,
2612
+ preparation: CampaignPortfolioLearningPreparation,
2613
+ ) -> None: ...
2614
+
2615
+ def abort_portfolio_generation_close(
2616
+ self,
2617
+ preparation: CampaignPortfolioLearningPreparation,
2618
+ ) -> None: ...
2619
+
2620
+
2621
+ @runtime_checkable
2622
+ class CampaignRecombinationUtilityBinder(Protocol):
2623
+ """Bind exact scored-source/pair utility under the generation cutoff.
2624
+
2625
+ ``source_result.candidates`` is the ranked ITT population;
2626
+ ``source_result.scored_candidates`` is the only utility/parent universe.
2627
+ """
2628
+
2629
+ def bind(
2630
+ self,
2631
+ *,
2632
+ source_archive_utility: ArchiveUtilitySnapshot,
2633
+ source_wave: PortfolioVariationWaveRequest,
2634
+ source_result: PortfolioVariationWaveResult,
2635
+ ) -> FrozenArchiveSourceUtilityReceipt: ...
2636
+
2637
+
2638
+ @runtime_checkable
2639
+ class CampaignOwnedRuntimeResourcePort(Protocol):
2640
+ """Close a provider queue or other resource explicitly owned by the adapter."""
2641
+
2642
+ async def close(self) -> FrozenJsonObject: ...
2643
+
2644
+
2645
+ @dataclass(slots=True)
2646
+ class AgenticPortfolioCampaignRuntime:
2647
+ """Run a prepared campaign through the real AgentEvolve application stack."""
2648
+
2649
+ prepared: PreparedEvolutionCampaign
2650
+ workload_config: AgenticCampaignWorkloadConfig
2651
+ workload_ports: CampaignWorkloadPorts
2652
+ composition: PortfolioEvolutionComposition
2653
+ parent_selector: CampaignParentSelectionPort
2654
+ wave_factory: CampaignPortfolioWaveFactory
2655
+ task_sha256: str
2656
+ parent_measurement_projection: ParentMeasurementProjection | None = None
2657
+ archive_context_projector: CampaignPortfolioArchiveContextProjector | None = None
2658
+ context_enricher: CampaignPortfolioContextEnricher | None = None
2659
+ contextual_search_planner: CampaignContextualSearchPlanner | None = None
2660
+ frontier_target_allocator: CampaignPortfolioFrontierTargetAllocator | None = None
2661
+ variation_envelope: CampaignVariationEnvelopePolicy | None = None
2662
+ memory_estimand_projector: CampaignPortfolioMemoryEstimandProjector | None = None
2663
+ learning_lifecycle: CampaignLearningLifecyclePort | None = None
2664
+ identifiable_reflection_executor: CampaignReflectionExecutor | None = None
2665
+ identifiable_reflection_evidence_source: (
2666
+ CampaignIdentifiableReflectionEvidenceSource | None
2667
+ ) = None
2668
+ legacy_recombination_reflection_executor: (
2669
+ CampaignLegacyRecombinationReflectionExecutor | None
2670
+ ) = None
2671
+ # Temporary source-compatible alias. It is always interpreted as the
2672
+ # legacy recombination port and cannot be combined with the identifiable
2673
+ # pair or the explicitly named legacy field.
2674
+ reflection_executor: CampaignLegacyRecombinationReflectionExecutor | None = None
2675
+ outcome_updater: CampaignPortfolioOutcomeUpdater | None = None
2676
+ recombination_utility_binder: CampaignRecombinationUtilityBinder | None = None
2677
+ owned_resources: CampaignOwnedRuntimeResourcePort | None = None
2678
+ recombination: PortfolioRecombination | None = None
2679
+ capacity_recourse: CampaignCapacityRecoursePort | None = None
2680
+ recombination_evaluation_allocation_mode: RecombinationEvaluationAllocationMode = (
2681
+ RecombinationEvaluationAllocationMode.NATIVE_THEN_RECOURSE
2682
+ )
2683
+ selector_request_prompt_renderer: CampaignSelectorRequestPromptRenderer | None = (
2684
+ None
2685
+ )
2686
+ wave_preparation_observer: CampaignPortfolioWavePreparationObserver | None = None
2687
+ archive: ParetoArchive = field(init=False)
2688
+ _history: list[EvolutionCandidate] = field(init=False, default_factory=list)
2689
+ _memory: FrozenJsonObject | None = field(init=False, default=None)
2690
+ _portfolio_waves: dict[
2691
+ int,
2692
+ tuple[tuple[PortfolioVariationWaveRequest, PortfolioVariationWaveResult], ...],
2693
+ ] = field(init=False, default_factory=dict)
2694
+ _recombination_results: dict[int, tuple[PortfolioRecombinationWaveResult, ...]] = (
2695
+ field(init=False, default_factory=dict)
2696
+ )
2697
+ _recombination_source_portfolio_generations: dict[int, int] = field(
2698
+ init=False,
2699
+ default_factory=dict,
2700
+ )
2701
+ _stage_receipts: dict[int, CampaignStageReceipt] = field(
2702
+ init=False, default_factory=dict
2703
+ )
2704
+ _parent_selection_progress: list[CampaignParentSelectionProgress] = field(
2705
+ init=False,
2706
+ default_factory=list,
2707
+ )
2708
+ _pending_parent_selection_progress: (
2709
+ _PendingCampaignParentSelectionProgress | None
2710
+ ) = field(init=False, default=None)
2711
+ _archive_utilities: dict[int, ArchiveUtilitySnapshot] = field(
2712
+ init=False, default_factory=dict
2713
+ )
2714
+ _reflection_results: dict[str, FrozenJsonObject] = field(
2715
+ init=False, default_factory=dict
2716
+ )
2717
+ _reflection_learning_evidence: dict[str, FrozenJsonObject] = field(
2718
+ init=False,
2719
+ default_factory=dict,
2720
+ )
2721
+ _identifiable_reflection_inputs: dict[
2722
+ str,
2723
+ CampaignIdentifiableReflectionInput,
2724
+ ] = field(init=False, default_factory=dict)
2725
+ _identifiable_reflection_abstentions: dict[str, FrozenJsonObject] = field(
2726
+ init=False,
2727
+ default_factory=dict,
2728
+ )
2729
+ _consumed_reflection_source_evidence_ids: set[str] = field(
2730
+ init=False,
2731
+ default_factory=set,
2732
+ )
2733
+ _last_identifiable_reflection_cutoff: int = field(init=False, default=0)
2734
+ _test_eligible_reflection_hashes: set[str] = field(init=False, default_factory=set)
2735
+ _started: bool = field(init=False, default=False)
2736
+ _cleaned: bool = field(init=False, default=False)
2737
+ _selector_calls: int = field(init=False, default=0)
2738
+ _wave_preparation_receipts: list[CampaignPortfolioWavePreparationReceipt] = field(
2739
+ init=False, default_factory=list
2740
+ )
2741
+ _phenotype_identity_cache: dict[str, str] = field(
2742
+ init=False,
2743
+ default_factory=dict,
2744
+ )
2745
+
2746
+ def __post_init__(self) -> None:
2747
+ if type(self.prepared) is not PreparedEvolutionCampaign:
2748
+ raise TypeError("prepared must be an exact PreparedEvolutionCampaign")
2749
+ PreparedEvolutionCampaign.__post_init__(self.prepared)
2750
+ if type(self.workload_config) is not AgenticCampaignWorkloadConfig:
2751
+ raise TypeError("workload_config must be exact")
2752
+ AgenticCampaignWorkloadConfig.__post_init__(self.workload_config)
2753
+ if type(self.composition) is not PortfolioEvolutionComposition:
2754
+ raise TypeError("composition must be exact")
2755
+ PortfolioEvolutionComposition.__post_init__(self.composition)
2756
+ if self.composition.benchmark is not self.workload_config.benchmark:
2757
+ raise ValueError("composition is bound to a different benchmark object")
2758
+ if not isinstance(self.parent_selector, CampaignParentSelectionPort):
2759
+ raise TypeError(
2760
+ "parent_selector must implement CampaignParentSelectionPort"
2761
+ )
2762
+ if not isinstance(self.wave_factory, CampaignPortfolioWaveFactory):
2763
+ raise TypeError("wave_factory must implement CampaignPortfolioWaveFactory")
2764
+ if self.archive_context_projector is not None and not isinstance(
2765
+ self.archive_context_projector,
2766
+ CampaignPortfolioArchiveContextProjector,
2767
+ ):
2768
+ raise TypeError("archive_context_projector must implement its runtime port")
2769
+ if self.context_enricher is not None and not isinstance(
2770
+ self.context_enricher,
2771
+ CampaignPortfolioContextEnricher,
2772
+ ):
2773
+ raise TypeError("context_enricher must implement its runtime port")
2774
+ if (
2775
+ self.contextual_search_planner is not None
2776
+ and type(self.contextual_search_planner)
2777
+ is not CampaignContextualSearchPlanner
2778
+ ):
2779
+ raise TypeError("contextual_search_planner must be exact or None")
2780
+ if self.frontier_target_allocator is not None and not isinstance(
2781
+ self.frontier_target_allocator,
2782
+ CampaignPortfolioFrontierTargetAllocator,
2783
+ ):
2784
+ raise TypeError("frontier_target_allocator must implement its runtime port")
2785
+ if (
2786
+ self.contextual_search_planner is not None
2787
+ and self.frontier_target_allocator is not None
2788
+ ):
2789
+ raise ValueError(
2790
+ "contextual and standalone frontier target allocation are mutually "
2791
+ "exclusive"
2792
+ )
2793
+ if self.variation_envelope is not None and not isinstance(
2794
+ self.variation_envelope,
2795
+ CampaignVariationEnvelopePolicy,
2796
+ ):
2797
+ raise TypeError(
2798
+ "variation_envelope must implement CampaignVariationEnvelopePolicy"
2799
+ )
2800
+ if self.memory_estimand_projector is not None and not isinstance(
2801
+ self.memory_estimand_projector,
2802
+ CampaignPortfolioMemoryEstimandProjector,
2803
+ ):
2804
+ raise TypeError("memory_estimand_projector must implement its runtime port")
2805
+ if self.wave_preparation_observer is not None and not isinstance(
2806
+ self.wave_preparation_observer,
2807
+ CampaignPortfolioWavePreparationObserver,
2808
+ ):
2809
+ raise TypeError("wave_preparation_observer must implement its runtime port")
2810
+ if self.learning_lifecycle is not None and not isinstance(
2811
+ self.learning_lifecycle,
2812
+ CampaignLearningLifecyclePort,
2813
+ ):
2814
+ raise TypeError("learning_lifecycle must implement its runtime port")
2815
+ if self.selector_request_prompt_renderer is not None and not isinstance(
2816
+ self.selector_request_prompt_renderer,
2817
+ CampaignSelectorRequestPromptRenderer,
2818
+ ):
2819
+ raise TypeError(
2820
+ "selector_request_prompt_renderer must implement its runtime port"
2821
+ )
2822
+ identifiable_executor = self.identifiable_reflection_executor
2823
+ evidence_source = self.identifiable_reflection_evidence_source
2824
+ explicitly_legacy = self.legacy_recombination_reflection_executor
2825
+ compatibility_legacy = self.reflection_executor
2826
+ if identifiable_executor is not None and not isinstance(
2827
+ identifiable_executor,
2828
+ CampaignReflectionExecutor,
2829
+ ):
2830
+ raise TypeError(
2831
+ "identifiable_reflection_executor must implement its runtime port"
2832
+ )
2833
+ if evidence_source is not None and not isinstance(
2834
+ evidence_source,
2835
+ CampaignIdentifiableReflectionEvidenceSource,
2836
+ ):
2837
+ raise TypeError(
2838
+ "identifiable_reflection_evidence_source must implement its runtime port"
2839
+ )
2840
+ if evidence_source is not None:
2841
+ for name in (
2842
+ "campaign_sha256",
2843
+ "workload_instance_sha256",
2844
+ "evaluator_contract_sha256",
2845
+ ):
2846
+ _require_sha256(
2847
+ getattr(evidence_source, name),
2848
+ f"identifiable_reflection_evidence_source.{name}",
2849
+ )
2850
+ if (identifiable_executor is None) != (evidence_source is None):
2851
+ raise ValueError(
2852
+ "identifiable reflection requires both executor and evidence source"
2853
+ )
2854
+ if explicitly_legacy is not None and not isinstance(
2855
+ explicitly_legacy,
2856
+ CampaignLegacyRecombinationReflectionExecutor,
2857
+ ):
2858
+ raise TypeError(
2859
+ "legacy_recombination_reflection_executor must implement its runtime port"
2860
+ )
2861
+ if compatibility_legacy is not None and not isinstance(
2862
+ compatibility_legacy,
2863
+ CampaignLegacyRecombinationReflectionExecutor,
2864
+ ):
2865
+ raise TypeError(
2866
+ "reflection_executor compatibility alias must implement the legacy port"
2867
+ )
2868
+ if explicitly_legacy is not None and compatibility_legacy is not None:
2869
+ raise ValueError("legacy reflection executor was configured twice")
2870
+ if identifiable_executor is not None and (
2871
+ explicitly_legacy is not None or compatibility_legacy is not None
2872
+ ):
2873
+ raise ValueError(
2874
+ "identifiable and legacy reflection modes are mutually exclusive"
2875
+ )
2876
+ if self.outcome_updater is not None and not isinstance(
2877
+ self.outcome_updater, CampaignPortfolioOutcomeUpdater
2878
+ ):
2879
+ raise TypeError("outcome_updater must implement its runtime port")
2880
+ if self.recombination_utility_binder is not None and not isinstance(
2881
+ self.recombination_utility_binder,
2882
+ CampaignRecombinationUtilityBinder,
2883
+ ):
2884
+ raise TypeError(
2885
+ "recombination_utility_binder must implement its runtime port"
2886
+ )
2887
+ if self.capacity_recourse is not None:
2888
+ validate_campaign_capacity_recourse_port(self.capacity_recourse)
2889
+ if type(self.recombination_evaluation_allocation_mode) is not (
2890
+ RecombinationEvaluationAllocationMode
2891
+ ):
2892
+ raise TypeError(
2893
+ "recombination_evaluation_allocation_mode must be exact"
2894
+ )
2895
+ if (
2896
+ self.recombination_evaluation_allocation_mode
2897
+ is RecombinationEvaluationAllocationMode.RECOURSE_ONLY
2898
+ and self.capacity_recourse is None
2899
+ ):
2900
+ raise ValueError("recourse-only allocation requires a recourse port")
2901
+ if self.owned_resources is not None and not isinstance(
2902
+ self.owned_resources, CampaignOwnedRuntimeResourcePort
2903
+ ):
2904
+ raise TypeError("owned_resources must implement its runtime port")
2905
+ if (
2906
+ type(self.task_sha256) is not str
2907
+ or len(self.task_sha256) != 64
2908
+ or any(value not in "0123456789abcdef" for value in self.task_sha256)
2909
+ ):
2910
+ raise ValueError("task_sha256 must be a lowercase SHA-256 digest")
2911
+ if type(self.workload_ports) is not CampaignWorkloadPorts:
2912
+ raise TypeError("workload_ports must be exact")
2913
+ CampaignWorkloadPorts.__post_init__(self.workload_ports)
2914
+ if self.workload_ports.ports_sha256 != self.prepared.workload_ports_sha256:
2915
+ raise ValueError("runtime workload ports differ from preparation")
2916
+ if (
2917
+ self.prepared.benchmark_session.benchmark
2918
+ != self.workload_config.benchmark_record
2919
+ ):
2920
+ raise ValueError("prepared session describes a different benchmark")
2921
+ measurement_projection = self.parent_measurement_projection
2922
+ if measurement_projection is not None:
2923
+ if type(measurement_projection) is not ParentMeasurementProjection:
2924
+ raise TypeError("parent_measurement_projection must be exact or None")
2925
+ measurement_projection.__post_init__()
2926
+ benchmark_sha256 = typed_json_sha256(
2927
+ self.prepared.benchmark_session.benchmark
2928
+ )
2929
+ if (
2930
+ measurement_projection.benchmark_sha256 != benchmark_sha256
2931
+ or measurement_projection.session_sha256
2932
+ != self.prepared.benchmark_session.session_sha256
2933
+ ):
2934
+ raise ValueError(
2935
+ "parent measurement projection names a foreign campaign"
2936
+ )
2937
+ benchmark = self.workload_config.benchmark
2938
+ evaluator = benchmark.detailed_evaluator
2939
+ if (
2940
+ evaluator is None
2941
+ or (
2942
+ evaluator.evaluator_identity.evaluator_id,
2943
+ evaluator.evaluator_identity.evaluator_version,
2944
+ evaluator.evaluator_identity.evaluator_context_sha256,
2945
+ )
2946
+ != measurement_projection.evaluator_identity
2947
+ ):
2948
+ raise ValueError(
2949
+ "parent measurement projection differs from benchmark evaluator"
2950
+ )
2951
+ expected_resolution = (
2952
+ (
2953
+ EXACT_OBJECTIVE_RESOLUTION_POLICY_ID,
2954
+ EXACT_OBJECTIVE_RESOLUTION_POLICY_VERSION,
2955
+ EXACT_OBJECTIVE_RESOLUTION_DEFINITION_SHA256,
2956
+ )
2957
+ if benchmark.objective_resolution is None
2958
+ else objective_resolution_policy_metadata(
2959
+ benchmark.objective_resolution
2960
+ )
2961
+ )
2962
+ if (
2963
+ measurement_projection.objective_resolution_identity
2964
+ != expected_resolution
2965
+ ):
2966
+ raise ValueError(
2967
+ "parent measurement projection differs from objective resolution"
2968
+ )
2969
+ semantics = benchmark.optimization_semantics
2970
+ if semantics is not None and (
2971
+ measurement_projection.decision_metrics.optimization_semantics_definition_sha256
2972
+ != semantics.definition_sha256
2973
+ ):
2974
+ raise ValueError(
2975
+ "parent measurement metric schema differs from benchmark semantics"
2976
+ )
2977
+ self.archive = ParetoArchive(
2978
+ self.composition.benchmark.objectives,
2979
+ outcome_relation_binding=self.composition.outcome_relation,
2980
+ )
2981
+ if self.recombination is None:
2982
+ self.recombination = PortfolioRecombination(
2983
+ engine=self.composition.engine,
2984
+ ids=self.composition.id_factory,
2985
+ selection_limit=(self.prepared.protocol.recombinations_per_parent),
2986
+ )
2987
+ elif type(self.recombination) is not PortfolioRecombination:
2988
+ raise TypeError("recombination must be exact or None")
2989
+ if (
2990
+ self.recombination.engine is not self.composition.engine
2991
+ or self.recombination.ids is not self.composition.id_factory
2992
+ or self.recombination.selection_limit
2993
+ != self.prepared.protocol.recombinations_per_parent
2994
+ ):
2995
+ raise ValueError(
2996
+ "recombination is bound to a different composition or protocol"
2997
+ )
2998
+
2999
+ @property
3000
+ def history(self) -> tuple[EvolutionCandidate, ...]:
3001
+ return tuple(self._history)
3002
+
3003
+ @property
3004
+ def final_front(self) -> tuple[EvolutionCandidate, ...]:
3005
+ return self.archive.front
3006
+
3007
+ @property
3008
+ def wave_preparation_receipts(
3009
+ self,
3010
+ ) -> tuple[CampaignPortfolioWavePreparationReceipt, ...]:
3011
+ """Validated prospective waves, including waves from failed stages."""
3012
+
3013
+ return tuple(self._wave_preparation_receipts)
3014
+
3015
+ async def start(
3016
+ self,
3017
+ prepared: PreparedEvolutionCampaign,
3018
+ ) -> CampaignExecutionStartReceipt:
3019
+ if self._started:
3020
+ raise RuntimeError("campaign runtime is one-shot")
3021
+ if prepared.preparation_sha256 != self.prepared.preparation_sha256:
3022
+ raise ValueError("lifecycle start received a foreign preparation")
3023
+ cache = await self.composition.engine.evaluation_cache_snapshot()
3024
+ if _cache_misses(cache) != 0 or int(cache.get("hits") or 0) != 0:
3025
+ raise RuntimeError("campaign runtime requires a fresh evaluation cache")
3026
+
3027
+ candidates: list[EvolutionCandidate] = []
3028
+ receipts: list[CampaignSeedExecutionReceipt] = []
3029
+ for seed in prepared.seeds.seeds:
3030
+ before = _cache_misses(
3031
+ await self.composition.engine.evaluation_cache_snapshot()
3032
+ )
3033
+ configuration = thaw_json(seed.configuration)
3034
+ if type(configuration) is not dict:
3035
+ raise TypeError("campaign seed root must be an object")
3036
+ candidate = await self.composition.engine.register_seed(
3037
+ configuration,
3038
+ label=f"campaign_seed_{seed.seed_id}",
3039
+ )
3040
+ after = _cache_misses(
3041
+ await self.composition.engine.evaluation_cache_snapshot()
3042
+ )
3043
+ if after - before not in {0, 1}:
3044
+ raise RuntimeError("one seed produced invalid cache accounting")
3045
+ candidates.append(candidate)
3046
+ receipts.append(
3047
+ CampaignSeedExecutionReceipt(
3048
+ seed_id=seed.seed_id,
3049
+ configuration_sha256=seed.configuration_sha256,
3050
+ evaluated=True,
3051
+ unique_evaluation=after > before,
3052
+ valid=candidate.valid,
3053
+ failure_type=None if candidate.valid else "benchmark_invalid",
3054
+ evidence=_object({"candidate": _candidate_record(candidate)}),
3055
+ )
3056
+ )
3057
+
3058
+ self._memory = self.workload_ports.evidence.initialize_memory(
3059
+ self.prepared.benchmark_session,
3060
+ self.prepared.seeds,
3061
+ )
3062
+ for candidate in candidates:
3063
+ self._history.append(candidate)
3064
+ self.archive.consider(candidate)
3065
+ self._started = True
3066
+ return CampaignExecutionStartReceipt(
3067
+ preparation_sha256=prepared.preparation_sha256,
3068
+ runtime_preflight_receipt_sha256=(prepared.runtime_receipt.receipt_sha256),
3069
+ runtime_session_id=(f"campaign_runtime_{prepared.preparation_sha256[:24]}"),
3070
+ seed_batch_sha256=prepared.seeds.batch_sha256,
3071
+ seed_receipts=tuple(receipts),
3072
+ evidence=_object(
3073
+ {
3074
+ "runtime": "agentic_portfolio_campaign",
3075
+ "provider_calls_during_start": 0,
3076
+ "archive_snapshot_sha256": pareto_archive_snapshot_hash(
3077
+ self.archive.snapshot()
3078
+ ),
3079
+ "memory_sha256": typed_json_sha256(self._memory),
3080
+ }
3081
+ ),
3082
+ )
3083
+
3084
+ def _require_active(self) -> None:
3085
+ if not self._started or self._cleaned:
3086
+ raise RuntimeError("campaign runtime is not active")
3087
+
3088
+ def _archive_record(self) -> FrozenJsonObject:
3089
+ return self._archive_record_for(self.archive)
3090
+
3091
+ @staticmethod
3092
+ def _archive_record_for(archive: ParetoArchive) -> FrozenJsonObject:
3093
+ if type(archive) is not ParetoArchive:
3094
+ raise TypeError("archive must be an exact ParetoArchive")
3095
+ snapshot = archive.snapshot()
3096
+ return _object(
3097
+ {
3098
+ "snapshot_sha256": pareto_archive_snapshot_hash(snapshot),
3099
+ "summary": snapshot.to_trace_record(),
3100
+ "front_candidates": [
3101
+ _candidate_record(candidate)
3102
+ for candidate in snapshot.front_candidates
3103
+ ],
3104
+ }
3105
+ )
3106
+
3107
+ def _preview_archive(
3108
+ self,
3109
+ candidates: tuple[EvolutionCandidate, ...],
3110
+ ) -> ParetoArchive:
3111
+ """Replay then extend the archive without changing live campaign state."""
3112
+
3113
+ current = self.archive.snapshot()
3114
+ preview = ParetoArchive(
3115
+ current.objectives,
3116
+ evidence_admission_policy=current.evidence_admission_policy,
3117
+ outcome_relation_binding=self.archive.outcome_relation_binding,
3118
+ )
3119
+ for candidate in self._history:
3120
+ preview.consider(candidate)
3121
+ if pareto_archive_snapshot_hash(preview.snapshot()) != (
3122
+ pareto_archive_snapshot_hash(current)
3123
+ ):
3124
+ raise RuntimeError("campaign history cannot reproduce the live archive")
3125
+ for candidate in candidates:
3126
+ preview.consider(candidate)
3127
+ return preview
3128
+
3129
+ async def snapshot_archive(
3130
+ self,
3131
+ request: CampaignArchiveCutoffRequest,
3132
+ ) -> CampaignArchiveCutoffReceipt:
3133
+ self._require_active()
3134
+ expected_prior = self._stage_receipts.get(request.generation - 1)
3135
+ if request.prior_stage_receipt_sha256 != (
3136
+ None if expected_prior is None else expected_prior.receipt_sha256
3137
+ ):
3138
+ raise ValueError("archive request is not chained to the runtime stage")
3139
+ archive = self._archive_record()
3140
+ return CampaignArchiveCutoffReceipt(
3141
+ request_sha256=request.request_sha256,
3142
+ preparation_sha256=request.preparation_sha256,
3143
+ generation=request.generation,
3144
+ archive=archive,
3145
+ evidence=_object(
3146
+ {
3147
+ "cutoff_before_generation": request.generation,
3148
+ "archive_sha256": typed_json_sha256(archive),
3149
+ }
3150
+ ),
3151
+ )
3152
+
3153
+ async def _optimizer_state_async(self, generation: int) -> OptimizerState:
3154
+ snapshot = self.archive.snapshot()
3155
+ cache = await self.composition.engine.evaluation_cache_snapshot()
3156
+ return OptimizerState(
3157
+ generation=generation,
3158
+ candidates=tuple(self._history),
3159
+ archive=snapshot,
3160
+ archive_snapshot_hash=pareto_archive_snapshot_hash(snapshot),
3161
+ unique_evaluations=_cache_misses(cache),
3162
+ logical_llm_calls=self._selector_calls,
3163
+ )
3164
+
3165
+ def _known_phenotypes(self) -> tuple[str, ...]:
3166
+ policy = self.composition.benchmark.phenotype_identity
3167
+ expected_policy = (policy.policy_id, policy.policy_version)
3168
+ known: set[str] = set()
3169
+ for candidate in self._history:
3170
+ configuration_sha256 = candidate.occurrence.configuration_hash
3171
+ value_sha256 = self._phenotype_identity_cache.get(configuration_sha256)
3172
+ if value_sha256 is None:
3173
+ # Engine candidates retain an immutable typed-JSON snapshot, while
3174
+ # benchmark phenotype policies are defined over ordinary detached
3175
+ # candidate values. Keep that boundary identical to catalog and
3176
+ # engine evaluation calls; policies must never need to understand
3177
+ # AgentEvolve's internal FrozenJson containers.
3178
+ identity = policy.identify(thaw_json(candidate.configuration))
3179
+ if type(identity) is not PhenotypeIdentity:
3180
+ raise TypeError(
3181
+ "phenotype policy must return exact PhenotypeIdentity"
3182
+ )
3183
+ PhenotypeIdentity.__post_init__(identity)
3184
+ if (identity.policy_id, identity.policy_version) != expected_policy:
3185
+ raise ValueError("phenotype policy returned a foreign identity law")
3186
+ value_sha256 = identity.value_sha256
3187
+ self._phenotype_identity_cache[configuration_sha256] = value_sha256
3188
+ known.add(value_sha256)
3189
+ return tuple(sorted(known))
3190
+
3191
+ def _validate_wave(
3192
+ self,
3193
+ *,
3194
+ wave: PortfolioVariationWaveRequest,
3195
+ build: CampaignPortfolioWaveContext,
3196
+ ) -> None:
3197
+ if type(wave) is not PortfolioVariationWaveRequest:
3198
+ raise TypeError("wave factory must return PortfolioVariationWaveRequest")
3199
+ PortfolioVariationWaveRequest.__post_init__(wave)
3200
+ if (
3201
+ wave.parent is not build.parent
3202
+ or wave.generation != build.stage_request.step.generation
3203
+ or wave.selection_request.finite_variation_contract
3204
+ != build.variation.contract
3205
+ or wave.selection_request.portfolio_size
3206
+ != build.stage_request.step.offspring_per_parent
3207
+ ):
3208
+ raise ValueError("wave factory escaped its trusted campaign inputs")
3209
+ try:
3210
+ resolve_campaign_selector_context_extension(
3211
+ trusted_context=build.evidence_context,
3212
+ selector_context=wave.selection_request.context,
3213
+ )
3214
+ except (TypeError, ValueError) as error:
3215
+ raise ValueError(
3216
+ "wave factory escaped its trusted campaign inputs"
3217
+ ) from error
3218
+
3219
+ def _project_memory_estimand(
3220
+ self,
3221
+ build: CampaignPortfolioWaveContext,
3222
+ ) -> CampaignPortfolioWaveContext:
3223
+ """Install the sole dynamic core-owned selector-context projection."""
3224
+
3225
+ projector = self.memory_estimand_projector
3226
+ if projector is None:
3227
+ return build
3228
+ projection = projector.project(build)
3229
+ if projection is None:
3230
+ return build
3231
+ if type(projection) is not CampaignPortfolioMemoryEstimandProjection:
3232
+ raise TypeError(
3233
+ "memory estimand projector must return an exact projection or None"
3234
+ )
3235
+ CampaignPortfolioMemoryEstimandProjection.__post_init__(projection)
3236
+ projected_context = thaw_json(build.evidence_context)
3237
+ if type(projected_context) is not dict: # pragma: no cover - closed root.
3238
+ raise AssertionError("portfolio context did not thaw to an object")
3239
+ projected_context[MEMORY_ESTIMAND_CONTEXT_KEY] = thaw_json(
3240
+ projection.estimand_context
3241
+ )
3242
+ projected_context[MEMORY_ESTIMAND_STRATUM_SHA256_KEY] = (
3243
+ projection.estimand_stratum_sha256
3244
+ )
3245
+ return replace(
3246
+ build,
3247
+ evidence_context=_object(projected_context),
3248
+ )
3249
+
3250
+ @staticmethod
3251
+ def _selector_audit(
3252
+ *,
3253
+ generation: int,
3254
+ parent_slot: int,
3255
+ wave: PortfolioVariationWaveRequest,
3256
+ result: PortfolioVariationWaveResult,
3257
+ prior_audit_set_sha256: str,
3258
+ prompt_renderer: CampaignSelectorRequestPromptRenderer | None = None,
3259
+ ) -> CampaignSelectorAuditReceipt:
3260
+ decision = result.receipt
3261
+ response = result.selection_decision_audit_record
3262
+ if response is None:
3263
+ raise RuntimeError("fresh portfolio result omitted decision audit")
3264
+ request_text = (
3265
+ render_portfolio_selection_prompt(wave.selection_request)
3266
+ if prompt_renderer is None
3267
+ else prompt_renderer.render(wave.selection_request)
3268
+ )
3269
+ if type(request_text) is not str or not request_text:
3270
+ raise TypeError("selector prompt renderer must return non-empty text")
3271
+ plaintext = _object(
3272
+ {
3273
+ "selector_call_id": wave.selection_request.call_id.value,
3274
+ "request_sha256": wave.selection_request.request_sha256,
3275
+ "decision_sha256": decision.decision_sha256,
3276
+ **encode_selector_audit_text("request_text", request_text),
3277
+ **encode_selector_audit_text(
3278
+ "response_text",
3279
+ _canonical_text(response),
3280
+ ),
3281
+ "request_text_kind": "exact_framework_prompt",
3282
+ "response_text_kind": "trusted_structured_decision_projection",
3283
+ }
3284
+ )
3285
+ return CampaignSelectorAuditReceipt(
3286
+ generation=generation,
3287
+ parent_slot=parent_slot,
3288
+ selector_call_id=wave.selection_request.call_id.value,
3289
+ request_sha256=wave.selection_request.request_sha256,
3290
+ decision_sha256=decision.decision_sha256,
3291
+ trace_receipt_sha256=typed_json_sha256(plaintext),
3292
+ plaintext_audit=plaintext,
3293
+ prior_audit_set_sha256=prior_audit_set_sha256,
3294
+ execution_mode=SelectorAuditExecutionMode.FRESH,
3295
+ )
3296
+
3297
+ async def _portfolio_stage(
3298
+ self,
3299
+ request: CampaignStageRequest,
3300
+ *,
3301
+ cache_before: int,
3302
+ ) -> CampaignStageReceipt:
3303
+ step = request.step
3304
+ state = await self._optimizer_state_async(step.generation - 1)
3305
+ selection = self.parent_selector.select(
3306
+ state,
3307
+ task_sha256=self.task_sha256,
3308
+ parent_count=step.parent_count,
3309
+ rotation_index=(step.generation - 1) // 2,
3310
+ progress=tuple(self._parent_selection_progress),
3311
+ archive_utility=request.archive_utility,
3312
+ )
3313
+ if type(selection) is not CampaignParentSelection:
3314
+ raise TypeError("parent selector must return CampaignParentSelection")
3315
+ CampaignParentSelection.__post_init__(selection)
3316
+ if (
3317
+ len(selection.parents) != step.parent_count
3318
+ or len(selection.lanes) != step.parent_count
3319
+ or len(selection.decision_slots) != step.parent_count
3320
+ ):
3321
+ raise ValueError("parent selector did not satisfy the prepared stage")
3322
+ assert self._memory is not None
3323
+ if any(
3324
+ value not in self._test_eligible_reflection_hashes
3325
+ for value in request.test_eligible_reflection_receipt_sha256s
3326
+ ):
3327
+ raise RuntimeError(
3328
+ "portfolio stage names reflection evidence not admitted for testing"
3329
+ )
3330
+ known = self._known_phenotypes()
3331
+ builds: list[CampaignPortfolioWaveContext] = []
3332
+ build_hashes: list[tuple[str, str]] = []
3333
+ wave_preparations: list[CampaignPortfolioWavePreparationReceipt] = []
3334
+ lanes_by_id = {lane.lane_id: lane for lane in selection.lanes}
3335
+ lane_inputs = tuple(
3336
+ (
3337
+ parent_slot,
3338
+ decision_slot,
3339
+ lanes_by_id[decision_slot.lane_id],
3340
+ )
3341
+ for parent_slot, decision_slot in enumerate(selection.decision_slots)
3342
+ )
3343
+ base_variations = tuple(
3344
+ self.workload_ports.catalog.bind(
3345
+ self.prepared.benchmark_session.benchmark,
3346
+ parent_lane.parent.configuration,
3347
+ known,
3348
+ )
3349
+ for _, _, parent_lane in lane_inputs
3350
+ )
3351
+ variation_envelope_evidence: dict[str, object] | None = None
3352
+ variation_envelope_trace: dict[str, object] | None = None
3353
+ variations = base_variations
3354
+ if self.variation_envelope is not None:
3355
+ envelope_request = CampaignVariationEnvelopeRequest(
3356
+ campaign_scope_sha256=self.task_sha256,
3357
+ generation=step.generation,
3358
+ evaluation_slots_per_lane=step.offspring_per_parent,
3359
+ state=state,
3360
+ archive_utility=request.archive_utility,
3361
+ lanes=tuple(
3362
+ sorted(
3363
+ (
3364
+ CampaignVariationEnvelopeLane(
3365
+ lane_id=parent_lane.lane_id,
3366
+ parent=parent_lane.parent,
3367
+ base_variation=variation,
3368
+ )
3369
+ for (_, _, parent_lane), variation in zip(
3370
+ lane_inputs,
3371
+ base_variations,
3372
+ strict=True,
3373
+ )
3374
+ ),
3375
+ key=lambda value: value.lane_id,
3376
+ )
3377
+ ),
3378
+ )
3379
+ envelope_result = self.variation_envelope.enrich(envelope_request)
3380
+ validate_campaign_variation_envelope_result(
3381
+ policy=self.variation_envelope,
3382
+ request=envelope_request,
3383
+ result=envelope_result,
3384
+ )
3385
+ variation_by_lane = {
3386
+ value.lane_id: value.variation for value in envelope_result.lanes
3387
+ }
3388
+ variations = tuple(
3389
+ variation_by_lane[parent_lane.lane_id]
3390
+ for _, _, parent_lane in lane_inputs
3391
+ )
3392
+ variation_envelope_evidence = (
3393
+ campaign_variation_envelope_context_record(envelope_result)
3394
+ )
3395
+ variation_envelope_trace = campaign_variation_envelope_trace_record(
3396
+ request=envelope_request,
3397
+ result=envelope_result,
3398
+ )
3399
+
3400
+ for (
3401
+ parent_slot,
3402
+ decision_slot,
3403
+ parent_lane,
3404
+ ), base_variation, variation in zip(
3405
+ lane_inputs,
3406
+ base_variations,
3407
+ variations,
3408
+ strict=True,
3409
+ ):
3410
+ parent_lane = lanes_by_id[decision_slot.lane_id]
3411
+ parent = parent_lane.parent
3412
+ # Workload evidence is owned by the workload catalog authority and
3413
+ # therefore remains bound to its exact issued base view. External
3414
+ # proposal experts carry their own authenticated envelope evidence;
3415
+ # requiring a workload adapter to reissue every generic expert
3416
+ # union would couple the inverted API back to individual experts.
3417
+ evidence_context = self.workload_ports.evidence.context(
3418
+ self.prepared.benchmark_session,
3419
+ parent.configuration,
3420
+ base_variation,
3421
+ self._memory,
3422
+ )
3423
+ workload_context_sha256 = typed_json_sha256(evidence_context)
3424
+ parent_measurement = (
3425
+ None
3426
+ if self.parent_measurement_projection is None
3427
+ else bind_parent_measurement(
3428
+ candidate=parent,
3429
+ variation=variation,
3430
+ projection=self.parent_measurement_projection,
3431
+ )
3432
+ )
3433
+ evidence_context = attach_parent_measurement_to_context(
3434
+ evidence_context,
3435
+ parent_measurement,
3436
+ )
3437
+ portfolio_generations = tuple(
3438
+ step.generation
3439
+ for step in self.prepared.schedule.steps
3440
+ if step.kind is CampaignGenerationKind.PORTFOLIO
3441
+ )
3442
+ evidence_context = attach_campaign_search_phase_context(
3443
+ evidence_context,
3444
+ campaign_search_phase_context(
3445
+ campaign_generation=request.step.generation,
3446
+ portfolio_generations=portfolio_generations,
3447
+ ),
3448
+ )
3449
+ if variation_envelope_evidence is not None:
3450
+ envelope_context = thaw_json(evidence_context)
3451
+ if type(envelope_context) is not dict:
3452
+ raise TypeError("campaign evidence context must be an object")
3453
+ if CAMPAIGN_VARIATION_ENVELOPE_KEY in envelope_context:
3454
+ raise ValueError(
3455
+ "base context uses the reserved variation-envelope key"
3456
+ )
3457
+ envelope_context[CAMPAIGN_VARIATION_ENVELOPE_KEY] = (
3458
+ variation_envelope_evidence
3459
+ )
3460
+ evidence_context = _object(envelope_context)
3461
+ evidence_cards = self.workload_ports.evidence.cards(
3462
+ self.prepared.benchmark_session,
3463
+ parent.configuration,
3464
+ base_variation,
3465
+ self._memory,
3466
+ )
3467
+ build = CampaignPortfolioWaveContext(
3468
+ prepared=self.prepared,
3469
+ stage_request=request,
3470
+ parent_slot=parent_slot,
3471
+ parent=parent,
3472
+ variation=variation,
3473
+ evidence_context=evidence_context,
3474
+ evidence_cards=evidence_cards,
3475
+ memory=self._memory,
3476
+ parent_measurement=parent_measurement,
3477
+ test_eligible_reflections=tuple(
3478
+ (
3479
+ receipt_sha256,
3480
+ self._reflection_results[receipt_sha256],
3481
+ )
3482
+ for receipt_sha256 in (
3483
+ request.test_eligible_reflection_receipt_sha256s
3484
+ )
3485
+ ),
3486
+ parent_lane=parent_lane,
3487
+ decision_slot=decision_slot,
3488
+ )
3489
+ archive_projector = self.archive_context_projector
3490
+ if archive_projector is not None:
3491
+ projection = archive_projector.project(
3492
+ archive_utility=request.archive_utility,
3493
+ parent=parent,
3494
+ )
3495
+ if type(projection) is not (CampaignPortfolioArchiveContextProjection):
3496
+ raise TypeError(
3497
+ "archive context projector must return an exact projection"
3498
+ )
3499
+ CampaignPortfolioArchiveContextProjection.__post_init__(projection)
3500
+ if (
3501
+ projection.projector_id != archive_projector.projector_id
3502
+ or projection.projector_version
3503
+ != archive_projector.projector_version
3504
+ or projection.definition_sha256
3505
+ != archive_projector.definition_sha256
3506
+ or projection.archive_utility_snapshot_sha256
3507
+ != request.archive_utility.snapshot_sha256
3508
+ or projection.parent_configuration_sha256
3509
+ != parent.occurrence.configuration_hash
3510
+ ):
3511
+ raise ValueError(
3512
+ "archive context projection differs from its trusted inputs"
3513
+ )
3514
+ base_context = thaw_json(build.evidence_context)
3515
+ if type(base_context) is not dict: # pragma: no cover - closed root.
3516
+ raise AssertionError("portfolio context did not thaw to an object")
3517
+ if CAMPAIGN_ARCHIVE_CONTEXT_KEY in base_context:
3518
+ raise ValueError(
3519
+ "base context uses the reserved campaign-archive key"
3520
+ )
3521
+ base_context[CAMPAIGN_ARCHIVE_CONTEXT_KEY] = projection.to_record()
3522
+ build = replace(
3523
+ build,
3524
+ evidence_context=_object(base_context),
3525
+ archive_context=projection,
3526
+ )
3527
+ enricher = self.context_enricher
3528
+ if enricher is not None:
3529
+ contextual_history = enricher.enrich(build)
3530
+ if (
3531
+ type(contextual_history) is not FrozenJsonObject
3532
+ or freeze_json(contextual_history) is not contextual_history
3533
+ ):
3534
+ raise TypeError("context enricher must return a frozen object")
3535
+ base_context = thaw_json(build.evidence_context)
3536
+ if type(base_context) is not dict: # pragma: no cover - closed root.
3537
+ raise AssertionError("portfolio context did not thaw to an object")
3538
+ if CAMPAIGN_CONTEXTUAL_HISTORY_KEY in base_context:
3539
+ raise ValueError(
3540
+ "base context uses the reserved contextual-history key"
3541
+ )
3542
+ base_context[CAMPAIGN_CONTEXTUAL_HISTORY_KEY] = thaw_json(
3543
+ contextual_history
3544
+ )
3545
+ enriched_context = _object(base_context)
3546
+ build = replace(build, evidence_context=enriched_context)
3547
+ pre_memory_projection_context_sha256 = typed_json_sha256(
3548
+ build.evidence_context
3549
+ )
3550
+ build = self._project_memory_estimand(build)
3551
+ builds.append(build)
3552
+ build_hashes.append(
3553
+ (
3554
+ workload_context_sha256,
3555
+ pre_memory_projection_context_sha256,
3556
+ )
3557
+ )
3558
+
3559
+ planner = self.contextual_search_planner
3560
+ contextual_search_plan = None
3561
+ standalone_frontier_targets: tuple[
3562
+ CampaignPortfolioFrontierTarget, ...
3563
+ ] | None = None
3564
+ if planner is not None:
3565
+ contextual_search_plan = planner.plan(tuple(builds))
3566
+ contracts = {
3567
+ value.slice_id: value for value in contextual_search_plan.contracts
3568
+ }
3569
+ frontier_targets = {
3570
+ value.lane_id: value
3571
+ for value in contextual_search_plan.frontier_targets
3572
+ }
3573
+ lane_ids = tuple(value.parent_lane.lane_id for value in builds)
3574
+ if set(contracts) != set(lane_ids) or set(frontier_targets) != set(
3575
+ lane_ids
3576
+ ):
3577
+ raise ValueError(
3578
+ "contextual plan differs from the campaign parent lanes"
3579
+ )
3580
+ targeted_builds = []
3581
+ for value in builds:
3582
+ lane_id = value.parent_lane.lane_id
3583
+ target = frontier_targets[lane_id]
3584
+ evidence = thaw_json(value.evidence_context)
3585
+ if type(evidence) is not dict:
3586
+ raise TypeError("campaign evidence context must be an object")
3587
+ if CAMPAIGN_FRONTIER_TARGET_KEY in evidence:
3588
+ raise ValueError(
3589
+ "base context uses the reserved campaign frontier-target key"
3590
+ )
3591
+ evidence[CAMPAIGN_FRONTIER_TARGET_KEY] = target.to_record()
3592
+ targeted_builds.append(
3593
+ replace(
3594
+ value,
3595
+ evidence_context=_object(evidence),
3596
+ contextual_allocation=contracts[lane_id],
3597
+ frontier_target=target,
3598
+ )
3599
+ )
3600
+ builds = targeted_builds
3601
+ elif self.frontier_target_allocator is not None:
3602
+ allocator = self.frontier_target_allocator
3603
+ lanes = tuple(
3604
+ sorted(
3605
+ (
3606
+ (value.parent_lane.lane_id, value.parent)
3607
+ for value in builds
3608
+ ),
3609
+ key=lambda value: value[0],
3610
+ )
3611
+ )
3612
+ standalone_frontier_targets = allocator.allocate(
3613
+ archive_utility=request.archive_utility,
3614
+ lanes=lanes,
3615
+ )
3616
+ if type(standalone_frontier_targets) is not tuple or any(
3617
+ type(value) is not CampaignPortfolioFrontierTarget
3618
+ for value in standalone_frontier_targets
3619
+ ):
3620
+ raise TypeError(
3621
+ "frontier target allocator must return exact target receipts"
3622
+ )
3623
+ by_lane = {
3624
+ value.lane_id: value for value in standalone_frontier_targets
3625
+ }
3626
+ lane_ids = tuple(value[0] for value in lanes)
3627
+ if tuple(sorted(by_lane)) != lane_ids:
3628
+ raise ValueError(
3629
+ "standalone frontier targets differ from the campaign lanes"
3630
+ )
3631
+ targeted_builds = []
3632
+ for value in builds:
3633
+ lane_id = value.parent_lane.lane_id
3634
+ target = by_lane[lane_id]
3635
+ target.__post_init__()
3636
+ evidence = thaw_json(value.evidence_context)
3637
+ if type(evidence) is not dict:
3638
+ raise TypeError("campaign evidence context must be an object")
3639
+ if CAMPAIGN_FRONTIER_TARGET_KEY in evidence:
3640
+ raise ValueError(
3641
+ "base context uses the reserved campaign frontier-target key"
3642
+ )
3643
+ evidence[CAMPAIGN_FRONTIER_TARGET_KEY] = target.to_record()
3644
+ targeted_builds.append(
3645
+ replace(
3646
+ value,
3647
+ evidence_context=_object(evidence),
3648
+ frontier_target=target,
3649
+ )
3650
+ )
3651
+ builds = targeted_builds
3652
+
3653
+ factory = self.wave_factory
3654
+ if isinstance(factory, CampaignPortfolioWaveBatchFactory):
3655
+ built_waves = factory.build_batch(tuple(builds))
3656
+ if type(built_waves) is not tuple:
3657
+ raise TypeError("batch wave factory must return an exact tuple")
3658
+ if len(built_waves) != len(builds):
3659
+ raise ValueError("batch wave factory must preserve lane cardinality")
3660
+ else:
3661
+ built_waves = tuple(factory.build(build) for build in builds)
3662
+ if any(type(wave) is not PortfolioVariationWaveRequest for wave in built_waves):
3663
+ raise TypeError("wave factory returned a foreign wave request")
3664
+
3665
+ waves: list[PortfolioVariationWaveRequest] = []
3666
+ for build, wave, hashes in zip(
3667
+ builds,
3668
+ built_waves,
3669
+ build_hashes,
3670
+ strict=True,
3671
+ ):
3672
+ workload_context_sha256, pre_memory_projection_context_sha256 = hashes
3673
+ self._validate_wave(wave=wave, build=build)
3674
+ waves.append(wave)
3675
+ wave_preparations.append(
3676
+ _validated_wave_preparation_receipt(
3677
+ build=build,
3678
+ wave=wave,
3679
+ workload_context_sha256=workload_context_sha256,
3680
+ pre_memory_projection_context_sha256=(
3681
+ pre_memory_projection_context_sha256
3682
+ ),
3683
+ )
3684
+ )
3685
+ if len({wave.selection_request.call_id for wave in waves}) != len(waves):
3686
+ raise ValueError("portfolio stage reused a selector call ID")
3687
+
3688
+ # Preparation evidence is prospective audit state, not a sealed stage
3689
+ # result. Retain it even when provider execution or a later transaction
3690
+ # barrier fails. An injected observer may durably journal each receipt;
3691
+ # any observer failure aborts before gather_concurrent_stage can dispatch.
3692
+ self._wave_preparation_receipts.extend(wave_preparations)
3693
+ observer = self.wave_preparation_observer
3694
+ if observer is not None:
3695
+ for receipt in wave_preparations:
3696
+ observer.record_prepared_wave(receipt)
3697
+
3698
+ pending_results = await gather_concurrent_stage(
3699
+ self.composition.portfolio.run(
3700
+ wave,
3701
+ defer_memory_credit=True,
3702
+ )
3703
+ for wave in waves
3704
+ )
3705
+ if any(
3706
+ type(value) is not PortfolioVariationWaveResult for value in pending_results
3707
+ ):
3708
+ raise TypeError("portfolio service returned a foreign result")
3709
+ audits = tuple(
3710
+ self._selector_audit(
3711
+ generation=step.generation,
3712
+ parent_slot=slot,
3713
+ wave=wave,
3714
+ result=result,
3715
+ prior_audit_set_sha256=request.prior_selector_audit_set_sha256,
3716
+ prompt_renderer=self.selector_request_prompt_renderer,
3717
+ )
3718
+ for slot, (wave, result) in enumerate(
3719
+ zip(waves, pending_results, strict=True)
3720
+ )
3721
+ )
3722
+ memory_credit_preparation = (
3723
+ self.composition.portfolio.prepare_pending_memory_credit_batch(
3724
+ pending_results
3725
+ )
3726
+ )
3727
+ results = memory_credit_preparation.prepared_results
3728
+ memory_credit_batch = memory_credit_preparation.batch_receipt
3729
+ candidates = tuple(
3730
+ candidate for result in results for candidate in result.candidates
3731
+ )
3732
+ scored_candidate_count = sum(
3733
+ len(result.scored_candidates) for result in results
3734
+ )
3735
+ infeasible_candidate_count = sum(
3736
+ len(result.infeasible_candidates) for result in results
3737
+ )
3738
+ if scored_candidate_count + infeasible_candidate_count != len(candidates):
3739
+ raise RuntimeError(
3740
+ "portfolio disposition partition does not cover ranked ITT candidates"
3741
+ )
3742
+ prior_archive_decision_count = len(self.archive.decisions)
3743
+ preview_archive = self._preview_archive(candidates)
3744
+ archive_decisions = preview_archive.decisions[prior_archive_decision_count:]
3745
+ prior_memory = self._memory
3746
+ updater = self.outcome_updater
3747
+ learning = self.learning_lifecycle
3748
+ wave_request_sha256s = tuple(
3749
+ value.selection_request.request_sha256 for value in waves
3750
+ )
3751
+ result_receipt_sha256s = tuple(
3752
+ value.receipt.receipt_sha256 for value in results
3753
+ )
3754
+ outcome_preparation: CampaignPortfolioOutcomePreparation | None = None
3755
+ learning_preparation: CampaignPortfolioLearningPreparation | None = None
3756
+ try:
3757
+ if updater is not None:
3758
+ outcome_preparation = await updater.prepare_update(
3759
+ request,
3760
+ tuple(waves),
3761
+ results,
3762
+ prior_memory,
3763
+ )
3764
+ if type(outcome_preparation) is not CampaignPortfolioOutcomePreparation:
3765
+ raise TypeError(
3766
+ "outcome updater must return exact preparation evidence"
3767
+ )
3768
+ CampaignPortfolioOutcomePreparation.__post_init__(outcome_preparation)
3769
+ if (
3770
+ outcome_preparation.request_sha256 != request.request_sha256
3771
+ or outcome_preparation.generation != step.generation
3772
+ or outcome_preparation.wave_request_sha256s != wave_request_sha256s
3773
+ or outcome_preparation.result_receipt_sha256s
3774
+ != result_receipt_sha256s
3775
+ or outcome_preparation.prior_memory_sha256
3776
+ != typed_json_sha256(prior_memory)
3777
+ ):
3778
+ raise ValueError("outcome preparation differs from the stage")
3779
+ updated_memory = outcome_preparation.updated_memory
3780
+ else:
3781
+ updated_memory = prior_memory
3782
+ if learning is not None:
3783
+ learning_preparation = (
3784
+ await learning.prepare_portfolio_generation_close(
3785
+ request,
3786
+ tuple(waves),
3787
+ results,
3788
+ memory_credit_preparation,
3789
+ )
3790
+ )
3791
+ if type(learning_preparation) is not (
3792
+ CampaignPortfolioLearningPreparation
3793
+ ):
3794
+ raise TypeError(
3795
+ "learning lifecycle must return exact preparation evidence"
3796
+ )
3797
+ CampaignPortfolioLearningPreparation.__post_init__(learning_preparation)
3798
+ if (
3799
+ learning_preparation.request_sha256 != request.request_sha256
3800
+ or learning_preparation.generation != step.generation
3801
+ or learning_preparation.wave_request_sha256s != wave_request_sha256s
3802
+ or learning_preparation.result_receipt_sha256s
3803
+ != result_receipt_sha256s
3804
+ or learning_preparation.memory_credit_preparation_sha256
3805
+ != memory_credit_preparation.preparation_sha256
3806
+ ):
3807
+ raise ValueError("learning preparation differs from the stage")
3808
+ cache_after = _cache_misses(
3809
+ await self.composition.engine.evaluation_cache_snapshot()
3810
+ )
3811
+ receipt = CampaignStageReceipt(
3812
+ request_sha256=request.request_sha256,
3813
+ preparation_sha256=request.preparation_sha256,
3814
+ generation=step.generation,
3815
+ kind=step.kind,
3816
+ candidate_occurrence_count=len(candidates),
3817
+ unique_evaluation_count=cache_after - cache_before,
3818
+ selector_audits=audits,
3819
+ result=_object(
3820
+ {
3821
+ "parent_selection": thaw_json(selection.evidence),
3822
+ "parent_lanes": [
3823
+ value.to_record() for value in selection.lanes
3824
+ ],
3825
+ "decision_slots": [
3826
+ value.to_record() for value in selection.decision_slots
3827
+ ],
3828
+ "portfolio_wave_receipts": [
3829
+ value.receipt.to_record() for value in results
3830
+ ],
3831
+ "variation_envelope_trace_receipt": (
3832
+ variation_envelope_trace
3833
+ ),
3834
+ "candidates": [
3835
+ _candidate_record(candidate) for candidate in candidates
3836
+ ],
3837
+ "ranked_itt_candidate_count": len(candidates),
3838
+ "scored_candidate_count": scored_candidate_count,
3839
+ "candidate_infeasible_count": infeasible_candidate_count,
3840
+ "archive_candidate_decisions": [
3841
+ _archive_decision_record(value)
3842
+ for value in archive_decisions
3843
+ ],
3844
+ "archive_candidate_consideration_count": len(candidates),
3845
+ "candidate_infeasibility_recourse": (
3846
+ "retain_ranked_itt_reject_from_archive_no_resampling"
3847
+ ),
3848
+ "archive_after": thaw_json(
3849
+ self._archive_record_for(preview_archive)
3850
+ ),
3851
+ "memory_before_sha256": typed_json_sha256(prior_memory),
3852
+ "memory_after_sha256": typed_json_sha256(updated_memory),
3853
+ "memory_projection_updated": updater is not None,
3854
+ "outcome_update_preparation": (
3855
+ None
3856
+ if outcome_preparation is None
3857
+ else outcome_preparation.to_record()
3858
+ ),
3859
+ "context_enrichment_applied": (
3860
+ self.context_enricher is not None
3861
+ ),
3862
+ "archive_context_projection": (
3863
+ None
3864
+ if self.archive_context_projector is None
3865
+ else {
3866
+ "reserved_key": CAMPAIGN_ARCHIVE_CONTEXT_KEY,
3867
+ "projector_id": (
3868
+ self.archive_context_projector.projector_id
3869
+ ),
3870
+ "projector_version": (
3871
+ self.archive_context_projector.projector_version
3872
+ ),
3873
+ "definition_sha256": (
3874
+ self.archive_context_projector.definition_sha256
3875
+ ),
3876
+ }
3877
+ ),
3878
+ "contextual_search_plan": (
3879
+ None
3880
+ if contextual_search_plan is None
3881
+ else contextual_search_plan.to_record()
3882
+ ),
3883
+ **(
3884
+ {}
3885
+ if standalone_frontier_targets is None
3886
+ else {
3887
+ "standalone_frontier_targets": [
3888
+ value.to_record()
3889
+ for value in standalone_frontier_targets
3890
+ ]
3891
+ }
3892
+ ),
3893
+ "closed_loop_learning": (
3894
+ None
3895
+ if learning_preparation is None
3896
+ else learning_preparation.to_record()
3897
+ ),
3898
+ "memory_credit_preparation_sha256": (
3899
+ memory_credit_preparation.preparation_sha256
3900
+ ),
3901
+ "memory_credit_batch": (
3902
+ None
3903
+ if memory_credit_batch is None
3904
+ else memory_credit_batch.to_record()
3905
+ ),
3906
+ }
3907
+ ),
3908
+ )
3909
+ except BaseException:
3910
+ if (
3911
+ learning is not None
3912
+ and type(learning_preparation) is CampaignPortfolioLearningPreparation
3913
+ ):
3914
+ learning.abort_portfolio_generation_close(learning_preparation)
3915
+ if (
3916
+ updater is not None
3917
+ and type(outcome_preparation) is CampaignPortfolioOutcomePreparation
3918
+ ):
3919
+ updater.abort_update(outcome_preparation)
3920
+ raise
3921
+
3922
+ # All potentially fallible computation and receipt construction has
3923
+ # completed. The following methods are synchronous exact-publication
3924
+ # operations whose protocol forbids I/O and new validation.
3925
+ committed_results, committed_batch = (
3926
+ self.composition.portfolio.commit_prepared_memory_credit_batch(
3927
+ memory_credit_preparation
3928
+ )
3929
+ )
3930
+ if committed_results != results or committed_batch != memory_credit_batch:
3931
+ raise RuntimeError("memory commit differs from its stage preparation")
3932
+ if updater is not None:
3933
+ assert outcome_preparation is not None
3934
+ updater.commit_update(outcome_preparation)
3935
+ if learning is not None:
3936
+ assert learning_preparation is not None
3937
+ learning.commit_portfolio_generation_close(learning_preparation)
3938
+ self._history.extend(candidates)
3939
+ self.archive = preview_archive
3940
+ self._memory = updated_memory
3941
+ self._selector_calls += len(waves)
3942
+ self._portfolio_waves[step.generation] = tuple(zip(waves, results, strict=True))
3943
+ return receipt
3944
+
3945
+ async def _recombination_stage(
3946
+ self,
3947
+ request: CampaignStageRequest,
3948
+ *,
3949
+ cache_before: int,
3950
+ ) -> CampaignStageReceipt:
3951
+ step = request.step
3952
+ source_generation = step.source_portfolio_generation
3953
+ assert source_generation is not None
3954
+ source = self._portfolio_waves.get(source_generation)
3955
+ source_stage = self._stage_receipts.get(source_generation)
3956
+ if source is None or source_stage is None:
3957
+ raise RuntimeError("recombination has no in-memory source wave")
3958
+ if (
3959
+ request.source_portfolio is None
3960
+ or request.source_portfolio.receipt_sha256 != source_stage.receipt_sha256
3961
+ ):
3962
+ raise ValueError("recombination source receipt is not the runtime source")
3963
+ assert self.recombination is not None
3964
+ source_archive_utility = self._archive_utilities.get(source_generation)
3965
+ if source_archive_utility is None:
3966
+ raise RuntimeError("recombination lacks the source archive utility cutoff")
3967
+ utility_binder = self.recombination_utility_binder
3968
+ waves_list: list[PortfolioRecombinationWaveRequest] = []
3969
+ for slot, (wave, result) in enumerate(source):
3970
+ scored_source_count = len(result.scored_candidates)
3971
+ source_utilities = (
3972
+ None
3973
+ if utility_binder is None or scored_source_count < 2
3974
+ else utility_binder.bind(
3975
+ source_archive_utility=source_archive_utility,
3976
+ source_wave=wave,
3977
+ source_result=result,
3978
+ )
3979
+ )
3980
+ if (
3981
+ source_utilities is not None
3982
+ and type(source_utilities) is not FrozenArchiveSourceUtilityReceipt
3983
+ ):
3984
+ raise TypeError("utility binder returned a foreign receipt")
3985
+ waves_list.append(
3986
+ PortfolioRecombinationWaveRequest(
3987
+ source_wave=wave,
3988
+ source_result=result,
3989
+ ancestor=wave.parent,
3990
+ generation=step.generation,
3991
+ label_prefix=(f"campaign_g{step.generation:02d}_p{slot + 1:02d}"),
3992
+ phase="campaign_portfolio_recombination",
3993
+ source_archive_snapshot=(
3994
+ None if source_utilities is None else source_archive_utility
3995
+ ),
3996
+ source_utilities=source_utilities,
3997
+ )
3998
+ )
3999
+ waves = tuple(waves_list)
4000
+ allocation_mode = self.recombination_evaluation_allocation_mode
4001
+ results = (
4002
+ ()
4003
+ if allocation_mode
4004
+ is RecombinationEvaluationAllocationMode.RECOURSE_ONLY
4005
+ else await gather_concurrent_stage(
4006
+ self.recombination.run(wave) for wave in waves
4007
+ )
4008
+ )
4009
+ recombination_candidates = tuple(
4010
+ candidate for result in results for candidate in result.candidates
4011
+ )
4012
+ scored_candidate_count = sum(
4013
+ len(result.scored_candidates) for result in results
4014
+ )
4015
+ infeasible_candidate_count = sum(
4016
+ len(result.infeasible_candidates) for result in results
4017
+ )
4018
+ if scored_candidate_count + infeasible_candidate_count != len(
4019
+ recombination_candidates
4020
+ ):
4021
+ raise RuntimeError(
4022
+ "recombination disposition partition does not cover selected ITT "
4023
+ "candidates"
4024
+ )
4025
+ planned_capacity = step.planned_candidate_evaluations
4026
+ if len(recombination_candidates) > planned_capacity:
4027
+ raise RuntimeError("recombination exceeded its prepared capacity")
4028
+ recourse_result = None
4029
+ missing_capacity = planned_capacity - len(recombination_candidates)
4030
+ if missing_capacity and self.capacity_recourse is not None:
4031
+ preview = self._preview_archive(recombination_candidates)
4032
+ cache_at_recourse = _cache_misses(
4033
+ await self.composition.engine.evaluation_cache_snapshot()
4034
+ )
4035
+ recourse_state = OptimizerState(
4036
+ generation=step.generation,
4037
+ candidates=tuple((*self._history, *recombination_candidates)),
4038
+ archive=preview.snapshot(),
4039
+ archive_snapshot_hash=pareto_archive_snapshot_hash(
4040
+ preview.snapshot()
4041
+ ),
4042
+ unique_evaluations=cache_at_recourse,
4043
+ logical_llm_calls=self._selector_calls,
4044
+ )
4045
+ recourse_request = CampaignCapacityRecourseRequest(
4046
+ campaign_scope_sha256=self.task_sha256,
4047
+ preparation_sha256=request.preparation_sha256,
4048
+ stage_request_sha256=request.request_sha256,
4049
+ generation=step.generation,
4050
+ planned_candidate_occurrences=planned_capacity,
4051
+ realized_candidate_occurrences=len(recombination_candidates),
4052
+ state=recourse_state,
4053
+ )
4054
+ recourse_result = await self.capacity_recourse.fill(recourse_request)
4055
+ validate_campaign_capacity_recourse_result(
4056
+ port=self.capacity_recourse,
4057
+ request=recourse_request,
4058
+ result=recourse_result,
4059
+ )
4060
+ recourse_candidates = (
4061
+ () if recourse_result is None else recourse_result.candidates
4062
+ )
4063
+ candidates = tuple((*recombination_candidates, *recourse_candidates))
4064
+ for candidate in candidates:
4065
+ self._history.append(candidate)
4066
+ self.archive.consider(candidate)
4067
+ self._recombination_results[step.generation] = results
4068
+ self._recombination_source_portfolio_generations[step.generation] = (
4069
+ source_generation
4070
+ )
4071
+ contextual_post_recombination_credit = None
4072
+ contextual_post_stage_survival_credit = None
4073
+ planner = self.contextual_search_planner
4074
+ if planner is not None:
4075
+ source_wave_index = (source_generation + 1) // 2
4076
+ source_observations = tuple(
4077
+ value
4078
+ for value in planner.ledger.observations
4079
+ if value.campaign_scope_sha256 == planner.campaign_scope_sha256
4080
+ and value.wave_index == source_wave_index
4081
+ )
4082
+ if results:
4083
+ contextual_post_recombination_credit = (
4084
+ observe_contextual_post_recombination_credit(
4085
+ campaign_scope_sha256=planner.campaign_scope_sha256,
4086
+ source_wave_index=source_wave_index,
4087
+ observations=source_observations,
4088
+ results=results,
4089
+ post_stage_front_candidate_ids=tuple(
4090
+ sorted(value.candidate_id for value in self.archive.front)
4091
+ ),
4092
+ )
4093
+ )
4094
+ delayed_credits = contextual_post_recombination_credit.credits
4095
+ else:
4096
+ contextual_post_stage_survival_credit = (
4097
+ observe_contextual_post_stage_survival(
4098
+ campaign_scope_sha256=planner.campaign_scope_sha256,
4099
+ source_wave_index=source_wave_index,
4100
+ stage_request_sha256=request.request_sha256,
4101
+ observations=source_observations,
4102
+ post_stage_front_candidate_ids=tuple(
4103
+ sorted(value.candidate_id for value in self.archive.front)
4104
+ ),
4105
+ )
4106
+ )
4107
+ delayed_credits = contextual_post_stage_survival_credit.credits
4108
+ preview_ledger = type(planner.ledger)(
4109
+ observations=list(planner.ledger.observations),
4110
+ delayed_credits=list(planner.ledger.delayed_credits),
4111
+ allocation_realizations=list(planner.ledger.allocation_realizations),
4112
+ )
4113
+ preview_ledger.append_delayed_credit_batch(delayed_credits)
4114
+ planner.ledger.delayed_credits.extend(delayed_credits)
4115
+ cache_after = _cache_misses(
4116
+ await self.composition.engine.evaluation_cache_snapshot()
4117
+ )
4118
+ return CampaignStageReceipt(
4119
+ request_sha256=request.request_sha256,
4120
+ preparation_sha256=request.preparation_sha256,
4121
+ generation=step.generation,
4122
+ kind=step.kind,
4123
+ candidate_occurrence_count=len(candidates),
4124
+ unique_evaluation_count=cache_after - cache_before,
4125
+ selector_audits=(),
4126
+ result=_object(
4127
+ {
4128
+ "recombination_wave_receipts": [
4129
+ value.receipt.to_record() for value in results
4130
+ ],
4131
+ "candidates": [
4132
+ _candidate_record(candidate) for candidate in candidates
4133
+ ],
4134
+ "selected_itt_candidate_count": len(recombination_candidates),
4135
+ "scored_candidate_count": scored_candidate_count,
4136
+ "candidate_infeasible_count": infeasible_candidate_count,
4137
+ "candidate_infeasibility_recourse": (
4138
+ "retain_selected_itt_reject_from_archive_no_resampling"
4139
+ ),
4140
+ "archive_after": thaw_json(self._archive_record()),
4141
+ "capacity": {
4142
+ "allocation_mode": allocation_mode.value,
4143
+ "native_wave_count": len(waves),
4144
+ "native_wave_evaluation_suppressed": (
4145
+ allocation_mode
4146
+ is RecombinationEvaluationAllocationMode.RECOURSE_ONLY
4147
+ ),
4148
+ "planned_candidate_occurrences": planned_capacity,
4149
+ "recombination_candidate_occurrences": len(
4150
+ recombination_candidates
4151
+ ),
4152
+ "missing_candidate_occurrences": missing_capacity,
4153
+ "recourse_enabled": self.capacity_recourse is not None,
4154
+ "recourse_candidate_occurrences": len(
4155
+ recourse_candidates
4156
+ ),
4157
+ "realized_candidate_occurrences": len(candidates),
4158
+ "capacity_complete": len(candidates) == planned_capacity,
4159
+ "recourse_result": (
4160
+ None
4161
+ if recourse_result is None
4162
+ else recourse_result.to_record()
4163
+ ),
4164
+ },
4165
+ "archive_aware_source_utility": utility_binder is not None,
4166
+ "contextual_post_recombination_credit": (
4167
+ None
4168
+ if contextual_post_recombination_credit is None
4169
+ else contextual_post_recombination_credit.to_record()
4170
+ ),
4171
+ "contextual_post_stage_survival_credit": (
4172
+ None
4173
+ if contextual_post_stage_survival_credit is None
4174
+ else contextual_post_stage_survival_credit.to_record()
4175
+ ),
4176
+ }
4177
+ ),
4178
+ )
4179
+
4180
+ async def execute_stage(
4181
+ self,
4182
+ request: CampaignStageRequest,
4183
+ ) -> CampaignStageReceipt:
4184
+ self._require_active()
4185
+ if request.preparation_sha256 != self.prepared.preparation_sha256:
4186
+ raise ValueError("stage request names a foreign preparation")
4187
+ step = request.step
4188
+ if step.generation != len(self._stage_receipts) + 1:
4189
+ raise ValueError("stage request is not the next prepared generation")
4190
+ pre_archive_sha256 = typed_json_sha256(self._archive_record())
4191
+ if pre_archive_sha256 != request.archive_utility.archive_sha256:
4192
+ raise ValueError(
4193
+ "stage archive utility differs from the live pre-stage archive"
4194
+ )
4195
+ pending_progress = self._pending_parent_selection_progress
4196
+ if pending_progress is None:
4197
+ if step.generation != 1:
4198
+ raise RuntimeError("campaign parent progress lost its pending stage")
4199
+ else:
4200
+ if pending_progress.generation != step.generation - 1:
4201
+ raise RuntimeError("campaign parent progress is not contiguous")
4202
+ self._parent_selection_progress.append(
4203
+ pending_progress.complete(request.archive_utility)
4204
+ )
4205
+ self._pending_parent_selection_progress = None
4206
+ cache_before = _cache_misses(
4207
+ await self.composition.engine.evaluation_cache_snapshot()
4208
+ )
4209
+ if step.kind is CampaignGenerationKind.PORTFOLIO:
4210
+ receipt = await self._portfolio_stage(request, cache_before=cache_before)
4211
+ else:
4212
+ receipt = await self._recombination_stage(
4213
+ request,
4214
+ cache_before=cache_before,
4215
+ )
4216
+ post_archive_sha256 = typed_json_sha256(self._archive_record())
4217
+ result_record = thaw_json(receipt.result)
4218
+ result_archive_after = freeze_json(result_record.get("archive_after"))
4219
+ if (
4220
+ type(result_archive_after) is not FrozenJsonObject
4221
+ or typed_json_sha256(result_archive_after) != post_archive_sha256
4222
+ ):
4223
+ raise RuntimeError(
4224
+ "stage receipt archive-after evidence differs from live state"
4225
+ )
4226
+ pending_progress = _PendingCampaignParentSelectionProgress(
4227
+ generation=step.generation,
4228
+ stage_kind=step.kind,
4229
+ stage_request_sha256=request.request_sha256,
4230
+ stage_receipt_sha256=receipt.receipt_sha256,
4231
+ pre_archive_sha256=pre_archive_sha256,
4232
+ post_archive_sha256=post_archive_sha256,
4233
+ pre_utility=request.archive_utility,
4234
+ )
4235
+ self._archive_utilities[step.generation] = request.archive_utility
4236
+ self._stage_receipts[step.generation] = receipt
4237
+ self._pending_parent_selection_progress = pending_progress
4238
+ return receipt
4239
+
4240
+ def _validate_identifiable_reflection_lineage(
4241
+ self,
4242
+ reflection_input: CampaignIdentifiableReflectionInput,
4243
+ ) -> tuple[str, ...]:
4244
+ """Replay every projected contrast against the exact mutation wave.
4245
+
4246
+ This join happens inside the runtime, before the executor boundary. The
4247
+ executor consequently needs neither the portfolio results nor the later
4248
+ recombination results to obtain durable candidate/operator provenance.
4249
+ """
4250
+
4251
+ reflection_input.__post_init__()
4252
+ query = reflection_input.query
4253
+ source = self._portfolio_waves.get(query.source_portfolio_generation)
4254
+ if source is None:
4255
+ raise RuntimeError("identifiable reflection has no source portfolio")
4256
+ expected: dict[str, dict[str, object]] = {}
4257
+ for wave, result in source:
4258
+ decision = result.selection_decision
4259
+ if decision is None:
4260
+ raise RuntimeError(
4261
+ "identifiable reflection source omitted its ranked decision"
4262
+ )
4263
+ for selected, member, outcome in zip(
4264
+ decision.members,
4265
+ result.receipt.members,
4266
+ result.outcomes,
4267
+ strict=True,
4268
+ ):
4269
+ if member.disposition is not PortfolioMemberDisposition.SCORED:
4270
+ continue
4271
+ child = outcome.candidate
4272
+ if child is None: # Closed by result validation.
4273
+ raise AssertionError("scored mutation source lost its candidate")
4274
+ if member.outcome_sha256 in expected:
4275
+ raise RuntimeError("mutation source repeats outcome evidence")
4276
+ expected[member.outcome_sha256] = {
4277
+ "parent_candidate_id": wave.parent.candidate_id,
4278
+ "child_candidate_id": child.candidate_id,
4279
+ "operator_invocation_id": member.operator_invocation_id,
4280
+ "parent_configuration_sha256": (
4281
+ AuthenticatedHypothesisObservation.configuration_sha256(
4282
+ wave.parent.configuration
4283
+ )
4284
+ ),
4285
+ "child_configuration_sha256": (
4286
+ AuthenticatedHypothesisObservation.configuration_sha256(
4287
+ child.configuration
4288
+ )
4289
+ ),
4290
+ "option_id": selected.option_id,
4291
+ "option_identity_sha256": selected.option_identity_sha256,
4292
+ "option_family": selected.family,
4293
+ "finite_contract_identity_sha256": (
4294
+ wave.selection_request.finite_variation_contract.identity_sha256
4295
+ ),
4296
+ "changed_paths": member.materialization.changed_paths,
4297
+ }
4298
+ observed_source_ids: list[str] = []
4299
+ for contrast in reflection_input.evidence.contrasts:
4300
+ observed_source_ids.append(contrast.source_evidence_id)
4301
+ expected_lineage = expected.get(contrast.source_evidence_id)
4302
+ if expected_lineage is None:
4303
+ raise ValueError(
4304
+ "identifiable contrast is not a scored source mutation"
4305
+ )
4306
+ observed_lineage = {
4307
+ "parent_candidate_id": contrast.parent_candidate_id,
4308
+ "child_candidate_id": contrast.child_candidate_id,
4309
+ "operator_invocation_id": contrast.operator_invocation_id,
4310
+ "parent_configuration_sha256": (contrast.parent_configuration_sha256),
4311
+ "child_configuration_sha256": contrast.child_configuration_sha256,
4312
+ "option_id": contrast.option_id,
4313
+ "option_identity_sha256": contrast.option_identity_sha256,
4314
+ "option_family": contrast.option_family,
4315
+ "finite_contract_identity_sha256": (
4316
+ contrast.finite_contract_identity_sha256
4317
+ ),
4318
+ "changed_paths": (contrast.affected_path,),
4319
+ }
4320
+ if observed_lineage != expected_lineage:
4321
+ raise ValueError(
4322
+ "identifiable contrast candidate/operator lineage is foreign"
4323
+ )
4324
+ if contrast.event_index != query.sealed_cutoff_event_index_inclusive:
4325
+ raise ValueError(
4326
+ "identifiable contrast event differs from the source cutoff"
4327
+ )
4328
+ canonical_source_ids = tuple(sorted(observed_source_ids))
4329
+ if len(set(observed_source_ids)) != len(observed_source_ids):
4330
+ raise ValueError("identifiable contrast source evidence must be unique")
4331
+ if self._consumed_reflection_source_evidence_ids.intersection(
4332
+ canonical_source_ids
4333
+ ):
4334
+ raise ValueError("identifiable reflection attempted to reuse evidence")
4335
+ return canonical_source_ids
4336
+
4337
+ async def reflect(
4338
+ self,
4339
+ request: CampaignReflectionRequest,
4340
+ ) -> CampaignReflectionReceipt:
4341
+ self._require_active()
4342
+ if type(request) is not CampaignReflectionRequest:
4343
+ raise TypeError("request must be an exact CampaignReflectionRequest")
4344
+ CampaignReflectionRequest.__post_init__(request)
4345
+ source_generation = request.wave.source_generation
4346
+ source = self._recombination_results.get(source_generation)
4347
+ if source is None:
4348
+ raise RuntimeError("reflection has no exact recombination source")
4349
+ source_stage = self._stage_receipts.get(source_generation)
4350
+ if (
4351
+ source_stage is None
4352
+ or source_stage.receipt_sha256 != request.source_stage.receipt_sha256
4353
+ ):
4354
+ raise ValueError("reflection request differs from the runtime source stage")
4355
+ identifiable_executor = self.identifiable_reflection_executor
4356
+ evidence_source = self.identifiable_reflection_evidence_source
4357
+ reflection_input: CampaignIdentifiableReflectionInput | None = None
4358
+ if identifiable_executor is not None:
4359
+ if evidence_source is None: # Closed by construction; retained defensively.
4360
+ raise RuntimeError(
4361
+ "identifiable reflection omitted its evidence source"
4362
+ )
4363
+ source_portfolio_generation = (
4364
+ self._recombination_source_portfolio_generations.get(source_generation)
4365
+ )
4366
+ if source_portfolio_generation is None:
4367
+ raise RuntimeError(
4368
+ "identifiable reflection lacks its source portfolio cutoff"
4369
+ )
4370
+ query = CampaignIdentifiableReflectionEvidenceQuery(
4371
+ reflection_request_sha256=request.request_sha256,
4372
+ preparation_sha256=request.preparation_sha256,
4373
+ runtime_start_receipt_sha256=(request.runtime_start_receipt_sha256),
4374
+ campaign_sha256=evidence_source.campaign_sha256,
4375
+ workload_instance_sha256=(evidence_source.workload_instance_sha256),
4376
+ evaluator_contract_sha256=(evidence_source.evaluator_contract_sha256),
4377
+ wave=request.wave,
4378
+ source_stage_receipt_sha256=request.source_stage.receipt_sha256,
4379
+ source_portfolio_generation=source_portfolio_generation,
4380
+ prior_cutoff_event_index_exclusive=(
4381
+ self._last_identifiable_reflection_cutoff
4382
+ ),
4383
+ sealed_cutoff_event_index_inclusive=source_portfolio_generation,
4384
+ )
4385
+ if (
4386
+ request.request_sha256 in self._identifiable_reflection_inputs
4387
+ or request.request_sha256
4388
+ in self._identifiable_reflection_abstentions
4389
+ ):
4390
+ raise ValueError("reflection request was already executed")
4391
+ try:
4392
+ source_projection = evidence_source.project(query)
4393
+ except NoIdentifiableMutationEvidenceError as abstention:
4394
+ # E0 is decided entirely from the sealed committed registry.
4395
+ # Advance the cutoff and authenticate the abstention without
4396
+ # invoking the model executor or campaign-learning lifecycle.
4397
+ record = {
4398
+ **abstention.to_record(),
4399
+ "reflection_request_sha256": request.request_sha256,
4400
+ "query_sha256": query.query_sha256,
4401
+ "source_generation": source_generation,
4402
+ "prior_cutoff_event_index_exclusive": (
4403
+ query.prior_cutoff_event_index_exclusive
4404
+ ),
4405
+ "sealed_cutoff_event_index_inclusive": (
4406
+ query.sealed_cutoff_event_index_inclusive
4407
+ ),
4408
+ "source_stage_payload_exposed": False,
4409
+ "recombination_results_exposed": False,
4410
+ "learning_registration_permitted": False,
4411
+ "test_admission_permitted": False,
4412
+ }
4413
+ frozen_abstention = _object(record)
4414
+ self._identifiable_reflection_abstentions[
4415
+ request.request_sha256
4416
+ ] = frozen_abstention
4417
+ self._last_identifiable_reflection_cutoff = (
4418
+ source_portfolio_generation
4419
+ )
4420
+ return CampaignReflectionReceipt(
4421
+ request_sha256=request.request_sha256,
4422
+ preparation_sha256=request.preparation_sha256,
4423
+ source_generation=source_generation,
4424
+ source_stage_receipt_sha256=(
4425
+ request.source_stage.receipt_sha256
4426
+ ),
4427
+ logical_agent_calls=request.wave.call_count,
4428
+ visibility=(
4429
+ ReflectionVisibility.QUARANTINED_UNTIL_BLOCK_CLOSE
4430
+ ),
4431
+ status=CampaignReflectionStatus.ABSTAINED,
4432
+ failure_type=None,
4433
+ quarantined_result=frozen_abstention,
4434
+ )
4435
+ if type(source_projection) is not (
4436
+ CampaignIdentifiableReflectionEvidenceProjection
4437
+ ):
4438
+ raise TypeError(
4439
+ "identifiable evidence source returned a foreign projection"
4440
+ )
4441
+ reflection_input = CampaignIdentifiableReflectionInput(
4442
+ query=query,
4443
+ source=source_projection,
4444
+ )
4445
+ source_evidence_ids = self._validate_identifiable_reflection_lineage(
4446
+ reflection_input
4447
+ )
4448
+ # Consume before yielding to provider code. A failed or cancelled
4449
+ # call cannot be replayed as though the same cohort were new.
4450
+ self._identifiable_reflection_inputs[request.request_sha256] = (
4451
+ reflection_input
4452
+ )
4453
+ self._consumed_reflection_source_evidence_ids.update(source_evidence_ids)
4454
+ self._last_identifiable_reflection_cutoff = source_portfolio_generation
4455
+ result = await identifiable_executor.reflect(reflection_input)
4456
+ else:
4457
+ legacy_executor = self.legacy_recombination_reflection_executor
4458
+ if legacy_executor is None:
4459
+ legacy_executor = self.reflection_executor
4460
+ if legacy_executor is None:
4461
+ raise RuntimeError(
4462
+ "prepared schedule requires an injected reflection executor"
4463
+ )
4464
+ result = await legacy_executor.reflect(request, source)
4465
+ if type(result) is not FrozenJsonObject or freeze_json(result) is not result:
4466
+ raise TypeError("reflection executor must return a frozen object")
4467
+ if reflection_input is not None:
4468
+ result_record = thaw_json(result)
4469
+ if CAMPAIGN_IDENTIFIABLE_REFLECTION_BINDING_KEY in result_record:
4470
+ raise ValueError(
4471
+ "reflection executor attempted to author the runtime binding"
4472
+ )
4473
+ result_record[CAMPAIGN_IDENTIFIABLE_REFLECTION_BINDING_KEY] = {
4474
+ "schema_version": 1,
4475
+ "input_sha256": reflection_input.input_sha256,
4476
+ "query_sha256": reflection_input.query.query_sha256,
4477
+ "source_projection_sha256": (reflection_input.source.projection_sha256),
4478
+ "registry_snapshot_sha256": (
4479
+ reflection_input.source.registry_snapshot_sha256
4480
+ ),
4481
+ "evidence_snapshot_sha256": (reflection_input.evidence.snapshot_sha256),
4482
+ "prior_cutoff_event_index_exclusive": (
4483
+ reflection_input.query.prior_cutoff_event_index_exclusive
4484
+ ),
4485
+ "sealed_cutoff_event_index_inclusive": (
4486
+ reflection_input.query.sealed_cutoff_event_index_inclusive
4487
+ ),
4488
+ "source_stage_payload_exposed": False,
4489
+ "recombination_results_exposed": False,
4490
+ }
4491
+ result = _object(result_record)
4492
+ receipt = CampaignReflectionReceipt(
4493
+ request_sha256=request.request_sha256,
4494
+ preparation_sha256=request.preparation_sha256,
4495
+ source_generation=source_generation,
4496
+ source_stage_receipt_sha256=request.source_stage.receipt_sha256,
4497
+ logical_agent_calls=request.wave.call_count,
4498
+ visibility=ReflectionVisibility.QUARANTINED_UNTIL_BLOCK_CLOSE,
4499
+ status=CampaignReflectionStatus.COMPLETED,
4500
+ failure_type=None,
4501
+ quarantined_result=result,
4502
+ )
4503
+ learning = self.learning_lifecycle
4504
+ if learning is not None:
4505
+ evidence = learning.reflection_completed(request, receipt, result)
4506
+ if (
4507
+ type(evidence) is not FrozenJsonObject
4508
+ or freeze_json(evidence) is not evidence
4509
+ ):
4510
+ raise TypeError("learning reflection hook must return frozen evidence")
4511
+ self._reflection_learning_evidence[receipt.receipt_sha256] = evidence
4512
+ self._reflection_results[receipt.receipt_sha256] = result
4513
+ return receipt
4514
+
4515
+ async def admit_for_testing(
4516
+ self,
4517
+ request: CampaignReflectionTestAdmissionRequest,
4518
+ ) -> CampaignReflectionTestAdmissionReceipt:
4519
+ admitted = tuple(sorted(value.receipt_sha256 for value in request.reflections))
4520
+ eligible = tuple(
4521
+ sorted(
4522
+ {
4523
+ *request.previously_test_eligible_reflection_receipt_sha256s,
4524
+ *admitted,
4525
+ }
4526
+ )
4527
+ )
4528
+ if any(value not in self._reflection_results for value in admitted):
4529
+ raise RuntimeError("test admission names unavailable reflection content")
4530
+ learning_evidence: FrozenJsonObject | None = None
4531
+ learning = self.learning_lifecycle
4532
+ if learning is not None:
4533
+ contents = tuple(
4534
+ (
4535
+ reflection,
4536
+ self._reflection_results[reflection.receipt_sha256],
4537
+ )
4538
+ for reflection in sorted(
4539
+ request.reflections,
4540
+ key=lambda value: value.receipt_sha256,
4541
+ )
4542
+ )
4543
+ learning_evidence = learning.reflections_admitted(request, contents)
4544
+ if (
4545
+ type(learning_evidence) is not FrozenJsonObject
4546
+ or freeze_json(learning_evidence) is not learning_evidence
4547
+ ):
4548
+ raise TypeError("learning admission hook must return frozen evidence")
4549
+ self._test_eligible_reflection_hashes.update(admitted)
4550
+ return CampaignReflectionTestAdmissionReceipt(
4551
+ request_sha256=request.request_sha256,
4552
+ preparation_sha256=request.preparation_sha256,
4553
+ barrier_generation=request.barrier.generation,
4554
+ admitted_reflection_receipt_sha256s=admitted,
4555
+ test_eligible_reflection_receipt_sha256s=eligible,
4556
+ lifecycle_promoted=False,
4557
+ evidence=_object(
4558
+ {
4559
+ "admission_scope": "controlled_future_testing_only",
4560
+ "normal_retrieval_mutated": False,
4561
+ "learning_lifecycle": (
4562
+ None
4563
+ if learning_evidence is None
4564
+ else thaw_json(learning_evidence)
4565
+ ),
4566
+ "reflection_completion_learning": [
4567
+ {
4568
+ "reflection_receipt_sha256": value,
4569
+ "evidence": thaw_json(
4570
+ self._reflection_learning_evidence[value]
4571
+ ),
4572
+ }
4573
+ for value in admitted
4574
+ if value in self._reflection_learning_evidence
4575
+ ],
4576
+ }
4577
+ ),
4578
+ )
4579
+
4580
+ async def finalize(
4581
+ self,
4582
+ request: CampaignFinalizationRequest,
4583
+ ) -> CampaignFinalizationReceipt:
4584
+ contextual_terminal_credit = None
4585
+ planner = self.contextual_search_planner
4586
+ if (
4587
+ planner is not None
4588
+ and request.status is CampaignExecutionStatus.COMPLETED
4589
+ and planner.ledger.observations
4590
+ ):
4591
+ contextual_terminal_credit = observe_contextual_terminal_persistence(
4592
+ campaign_scope_sha256=planner.campaign_scope_sha256,
4593
+ available_at_wave_index=(
4594
+ len(self.prepared.schedule.portfolio_generations) + 2
4595
+ ),
4596
+ finalization_request_sha256=request.request_sha256,
4597
+ observations=tuple(planner.ledger.observations),
4598
+ terminal_front_candidate_ids=tuple(
4599
+ sorted(value.candidate_id for value in self.archive.front)
4600
+ ),
4601
+ )
4602
+ preview_ledger = type(planner.ledger)(
4603
+ observations=list(planner.ledger.observations),
4604
+ delayed_credits=list(planner.ledger.delayed_credits),
4605
+ allocation_realizations=list(planner.ledger.allocation_realizations),
4606
+ )
4607
+ preview_ledger.append_delayed_credit_batch(
4608
+ contextual_terminal_credit.credits
4609
+ )
4610
+ planner.ledger.delayed_credits.extend(contextual_terminal_credit.credits)
4611
+ return CampaignFinalizationReceipt(
4612
+ request_sha256=request.request_sha256,
4613
+ preparation_sha256=request.preparation_sha256,
4614
+ status=request.status,
4615
+ evidence=_object(
4616
+ {
4617
+ "stage_count": len(self._stage_receipts),
4618
+ "history_size": len(self._history),
4619
+ "final_archive": thaw_json(self._archive_record()),
4620
+ "parent_selection_progress": [
4621
+ value.to_record() for value in self._parent_selection_progress
4622
+ ],
4623
+ "reflection_learning_evidence": [
4624
+ {
4625
+ "reflection_receipt_sha256": receipt_sha256,
4626
+ "evidence": thaw_json(evidence),
4627
+ }
4628
+ for receipt_sha256, evidence in sorted(
4629
+ self._reflection_learning_evidence.items()
4630
+ )
4631
+ ],
4632
+ "reflection_evidence_mode": (
4633
+ "identifiable_direct_mutation"
4634
+ if self.identifiable_reflection_executor is not None
4635
+ else (
4636
+ "legacy_recombination"
4637
+ if self.legacy_recombination_reflection_executor is not None
4638
+ or self.reflection_executor is not None
4639
+ else "not_configured"
4640
+ )
4641
+ ),
4642
+ "identifiable_reflection_inputs": [
4643
+ value.to_record()
4644
+ for _, value in sorted(
4645
+ self._identifiable_reflection_inputs.items()
4646
+ )
4647
+ ],
4648
+ "identifiable_reflection_abstentions": [
4649
+ thaw_json(value)
4650
+ for _, value in sorted(
4651
+ self._identifiable_reflection_abstentions.items()
4652
+ )
4653
+ ],
4654
+ "contextual_terminal_persistence_credit": (
4655
+ None
4656
+ if contextual_terminal_credit is None
4657
+ else contextual_terminal_credit.to_record()
4658
+ ),
4659
+ }
4660
+ ),
4661
+ )
4662
+
4663
+ async def cleanup(
4664
+ self,
4665
+ request: CampaignCleanupRequest,
4666
+ ) -> CampaignCleanupReceipt:
4667
+ resource_evidence: FrozenJsonObject
4668
+ if self.owned_resources is None:
4669
+ resource_evidence = _object(
4670
+ {
4671
+ "ownership": "external_to_adapter",
4672
+ "adapter_owned_resource_count": 0,
4673
+ "external_resource_close_required_by_caller": True,
4674
+ }
4675
+ )
4676
+ else:
4677
+ resource_evidence = await self.owned_resources.close()
4678
+ if (
4679
+ type(resource_evidence) is not FrozenJsonObject
4680
+ or freeze_json(resource_evidence) is not resource_evidence
4681
+ ):
4682
+ raise TypeError("owned-resource close must return a frozen object")
4683
+ self._cleaned = True
4684
+ return CampaignCleanupReceipt(
4685
+ request_sha256=request.request_sha256,
4686
+ preparation_sha256=request.preparation_sha256,
4687
+ released=True,
4688
+ evidence=_object(
4689
+ {
4690
+ "resource_cleanup": thaw_json(resource_evidence),
4691
+ "runtime_closed": True,
4692
+ }
4693
+ ),
4694
+ )
4695
+
4696
+
4697
+ __all__ = [
4698
+ "AgenticPortfolioCampaignRuntime",
4699
+ "ArchiveDiverseEliteCampaignParentSelector",
4700
+ "ArchiveEliteCampaignParentSelector",
4701
+ "ArchiveEliteExplorerCampaignParentSelector",
4702
+ "ArchiveReservoirCampaignParentSelector",
4703
+ "ResidualHypervolumeCampaignParentSelector",
4704
+ "StagnationAwareDiverseCampaignParentSelector",
4705
+ "CAMPAIGN_CONTEXTUAL_HISTORY_KEY",
4706
+ "CAMPAIGN_ARCHIVE_CONTEXT_KEY",
4707
+ "CAMPAIGN_FRONTIER_TARGET_KEY",
4708
+ "CAMPAIGN_IDENTIFIABLE_REFLECTION_BINDING_KEY",
4709
+ "MEMORY_ESTIMAND_STRATUM_SHA256_KEY",
4710
+ "CampaignDecisionSlot",
4711
+ "CampaignIdentifiableReflectionEvidenceProjection",
4712
+ "CampaignIdentifiableReflectionEvidenceQuery",
4713
+ "CampaignIdentifiableReflectionEvidenceSource",
4714
+ "CampaignIdentifiableReflectionInput",
4715
+ "CampaignLearningLifecyclePort",
4716
+ "CampaignOwnedRuntimeResourcePort",
4717
+ "CampaignParentLane",
4718
+ "CampaignParentSelection",
4719
+ "CampaignParentSelectionProgress",
4720
+ "CampaignParentSelectionPort",
4721
+ "CampaignPortfolioContextEnricher",
4722
+ "CampaignPortfolioMemoryEstimandProjection",
4723
+ "CampaignPortfolioMemoryEstimandProjector",
4724
+ "CampaignPortfolioLearningPreparation",
4725
+ "CampaignPortfolioOutcomePreparation",
4726
+ "CampaignPortfolioWaveContext",
4727
+ "CampaignPortfolioWaveBatchFactory",
4728
+ "CampaignPortfolioWaveFactory",
4729
+ "CampaignPortfolioWavePreparationObserver",
4730
+ "CampaignPortfolioWavePreparationReceipt",
4731
+ "CampaignPortfolioOutcomeUpdater",
4732
+ "CampaignRecombinationUtilityBinder",
4733
+ "RecombinationEvaluationAllocationMode",
4734
+ "CampaignLegacyRecombinationReflectionExecutor",
4735
+ "CampaignReflectionFalsificationSource",
4736
+ "CampaignReflectionExecutor",
4737
+ "CampaignSelectorRequestPromptRenderer",
4738
+ "CommittedRegistryIdentifiableReflectionEvidenceSource",
4739
+ ]