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,742 @@
1
+ """A search loop that actually maintains a population and recombines it.
2
+
3
+ The loop in :mod:`agent_evolve.session.loop` renders the Pareto front as text
4
+ and asks a model to author whole configurations. That is whole-artifact rewrite;
5
+ it has no parent selection, no recombination, no mutation and no survival rule,
6
+ and it is measurably worse than uniform random sampling on every genome length
7
+ measured (advantage_theory sweep, 2026-08-03).
8
+
9
+ This loop supplies those operators. The seam that matters is
10
+ :class:`OperatorChooser`: something outside the loop decides *which parents* and
11
+ *where to cut*, and the loop never learns what made that decision. A random
12
+ chooser is the unguided control; a model-backed chooser is guidance. Neither can
13
+ author a candidate, because the chooser's return type cannot express one -- the
14
+ distinction is enforced by the type, not by convention.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import math
20
+ import random
21
+ from dataclasses import dataclass, field, replace
22
+ from typing import (
23
+ Any, Callable, Dict, List, Mapping, Optional, Sequence, Set)
24
+
25
+ from agent_evolve.core.problem import ObjectiveSpec
26
+ from agent_evolve.core.results import SearchResult, dominates
27
+ from agent_evolve.core.telemetry import harvest_telemetry
28
+ from agent_evolve.policies.genetic import (
29
+ Locus,
30
+ crossover,
31
+ loci_of,
32
+ mutate,
33
+ truncation_survival,
34
+ uniform_candidate,
35
+ )
36
+ from agent_evolve.session.evaluate import EvaluationCache, evaluate_batch
37
+
38
+ __all__ = ["OperatorChoice", "OperatorChooser", "GeneticConfig", "run_genetic_loop",
39
+ "random_chooser", "domination_rank"]
40
+
41
+ Config = Dict[str, Any]
42
+
43
+
44
+ @dataclass(frozen=True, slots=True)
45
+ class OperatorChoice:
46
+ """One offspring, expressed as operator arguments rather than a candidate.
47
+
48
+ There is deliberately no field that can hold a configuration. A chooser that
49
+ wanted to author a genome could not say so through this type.
50
+ """
51
+
52
+ parent_a: int
53
+ parent_b: int
54
+ mask: tuple[bool, ...]
55
+ mutate_loci: Optional[tuple[Locus, ...]] = None
56
+
57
+
58
+ #: Given the ranked population, how many offspring are wanted, and the search
59
+ #: state accumulated so far, return that many operator choices. Indices address
60
+ #: the population list. The state argument is explicit rather than captured so
61
+ #: that a chooser's inputs are visible at the call site and an ablation can be
62
+ #: read off the signature.
63
+ OperatorChooser = Callable[
64
+ [Sequence[tuple[Config, float]], int, Any], Sequence[OperatorChoice]
65
+ ]
66
+
67
+
68
+ @dataclass(slots=True)
69
+ class GeneticConfig:
70
+ population_size: int = 8
71
+ offspring_per_generation: int = 6
72
+ generations: int = 5
73
+ mutation_rate: Optional[float] = None # None -> 1/n_loci
74
+ seed: Optional[int] = None
75
+ seeds: tuple = ()
76
+ evaluation_budget: Optional[int] = None
77
+ evaluation_cache: EvaluationCache = field(default_factory=EvaluationCache)
78
+ #: What the chooser may reason over. Left None for the score-only baseline.
79
+ state: Any = None
80
+ #: Evaluations to spend on a screening design before the population is
81
+ #: built. 0 leaves the loop byte-identical to the pre-structure seam. The
82
+ #: screen is charged against the same budget as everything else: a phase
83
+ #: that spent free evaluations would not be a real operating point.
84
+ structure_budget: int = 0
85
+ #: Turns the screen's evidence into a prior. Defaults to the credential-free
86
+ #: statistical rule, so the phase is useful with no model at all -- and so a
87
+ #: model-proposed prior always has a rule to beat.
88
+ prior_proposer: Any = None
89
+ #: Pool sequence positions by field in the structure phase: the screen
90
+ #: becomes per-value pure/spiked designs and attribution counts every
91
+ #: (candidate, position) pair as one observation. The exchangeability
92
+ #: bet this makes is checked by the same unwind test as any prior.
93
+ structure_pooled: bool = False
94
+ #: A prior over WHERE to sample: narrows the declared domain of any locus it
95
+ #: names, for initialization and mutation alike. Left None, every sampler
96
+ #: sees exactly the schema's own domains, so the loop is byte-identical to
97
+ #: the pre-restriction seam. This is guidance over the sampling
98
+ #: distribution rather than over operator choice within a fixed one.
99
+ restriction: Any = None
100
+ #: Virtual pre-screening (a session.screening.Screening). Each generation
101
+ #: the loop builds pool_factor times the offspring it can afford, asks the
102
+ #: screen's validated surrogate to order the pool, and measures the
103
+ #: exploration floor plus the top of the order. None -- the default --
104
+ #: leaves the loop byte-identical to the pre-screening seam; the pool's
105
+ #: extra construction runs on its own RNG stream for the same reason.
106
+ screening: Any = None
107
+ #: Model-proposed initial population members, validated value-by-value
108
+ #: upstream. Inserted AFTER the caller's seeds and before schema-uniform
109
+ #: fill; () -- the default -- is byte-identical to the pre-init seam.
110
+ initial_proposals: tuple = ()
111
+ #: Variation-arm portfolio (a policies.operator_portfolio
112
+ #: .OperatorPortfolio). When present it constructs the generation's
113
+ #: offspring -- classical, rule, and authored arms under survival credit
114
+ #: -- and the loop reports each measured child's fate back to it. None
115
+ #: leaves offspring construction byte-identical to the classical path.
116
+ portfolio: Any = None
117
+ #: A model-authored SAMPLER (a policies.llm_generator.AuthoredGenerator).
118
+ #: When present it draws the whole generation's candidate POOL -- many
119
+ #: times the offspring the budget can afford -- and the pool then goes
120
+ #: through the same screening path as any other pool, or is taken from
121
+ #: the top when there is no screen. Mass generation charges nothing:
122
+ #: the generator never sees the problem or the cache, and only the
123
+ #: `want` candidates handed to measure() can reach the budget. None --
124
+ #: the default -- leaves the loop byte-identical to the pre-generator
125
+ #: seam. It replaces offspring construction, so it does not compose with
126
+ #: `portfolio`; asking for both is refused rather than silently ignored.
127
+ generator: Any = None
128
+ #: Whether the screen may spend the problem's CHEAPER evaluation fidelity
129
+ #: (``Problem.evaluate_proxy``), and how. "off" leaves the loop
130
+ #: byte-identical to the pre-proxy seam and is what every problem without
131
+ #: a cheap fidelity gets regardless. "rows" buys gate evidence at the
132
+ #: cheap fidelity, "screen" lets the cheap fidelity compete as a
133
+ #: surrogate under the gate, "both" does both. Proxy evaluations are
134
+ #: counted in their own ledger and are NEVER charged to
135
+ #: ``evaluation_budget``: the budget the claims are denominated in counts
136
+ #: real evaluations, and a cheap one is not one of them.
137
+ #:
138
+ #: The default is the MEASURED arm: "screen" won 23/24 seeds against the
139
+ #: no-proxy screen pooled over both budgets (12/0 at B=24, p = 0.00049;
140
+ #: 11/12 at B=16, p = 0.006; wave-K aug14_multifidelity.md), with the
141
+ #: shuffled-proxy control identical to no-proxy in all 12 runs -- the
142
+ #: gain is the cheap MEASUREMENT, not the machinery. The seam is active
143
+ #: iff the problem exposes ``evaluate_proxy``: ``ProxySource.for_problem``
144
+ #: returns None otherwise and nothing attaches, so every problem without
145
+ #: a cheap fidelity keeps the pre-proxy behaviour bit for bit.
146
+ proxy_fidelity: str = "screen"
147
+ #: A hard ceiling on proxy evaluations for the whole run. None -- no
148
+ #: ceiling. A consumer that exhausts it degrades to "no proxy".
149
+ proxy_ceiling: Optional[int] = None
150
+ #: MEASUREMENT-CONDITIONED REVISION of the sampling prior (a
151
+ #: policies.reguidance.Reguidance). Once per generation the policy is
152
+ #: asked whether its declared cadence has come round; when it has, one
153
+ #: model call reads what the run measured and returns a re-weighted prior
154
+ #: -- damped into the one in force, so a revision can tilt the draws and
155
+ #: cannot exclude anything -- plus, when it is bought, a few complete
156
+ #: configurations to measure next. None -- the default -- is
157
+ #: byte-identical to the pre-seam loop: no call fires and no counter
158
+ #: moves. The prior it revises is the same one initialization and
159
+ #: mutation already consult, so one revision reshapes every subsequent
160
+ #: draw with no further calls.
161
+ reguidance: Any = None
162
+
163
+
164
+ def domination_rank(
165
+ objectives_of: Sequence[Mapping[str, float]],
166
+ specs: Sequence[ObjectiveSpec],
167
+ ) -> List[float]:
168
+ """How many population members dominate each one. Lower is better.
169
+
170
+ Pareto-correct and weight-free. A scalarization would need weights nobody
171
+ declared, and an undeclared weight is precisely the kind of hidden choice
172
+ that has silently decided results in this project before.
173
+ """
174
+
175
+ def dominates(x: Mapping[str, float], y: Mapping[str, float]) -> bool:
176
+ better_anywhere = False
177
+ for spec in specs:
178
+ # spec.goal, read directly rather than via a default: a missing goal
179
+ # is a contract violation, and defaulting it to "min" would silently
180
+ # invert every maximised objective instead of failing.
181
+ sign = 1.0 if spec.goal == "min" else -1.0
182
+ xi, yi = sign * float(x[spec.name]), sign * float(y[spec.name])
183
+ if xi > yi:
184
+ return False
185
+ if xi < yi:
186
+ better_anywhere = True
187
+ return better_anywhere
188
+
189
+ return [
190
+ float(sum(1 for other in objectives_of if dominates(other, this)))
191
+ for this in objectives_of
192
+ ]
193
+
194
+
195
+ def random_chooser(rng: random.Random, n_loci: int) -> OperatorChooser:
196
+ """The unguided control: tournament parents, uniform mask, random loci."""
197
+
198
+ def choose(population: Sequence[tuple[Config, float]], count: int,
199
+ state: Any = None):
200
+ del state # the unguided control reasons over nothing
201
+ out: List[OperatorChoice] = []
202
+ for _ in range(count):
203
+ def pick() -> int:
204
+ a, b = rng.sample(range(len(population)), min(2, len(population))) \
205
+ if len(population) >= 2 else (0, 0)
206
+ return a if population[a][1] <= population[b][1] else b
207
+
208
+ out.append(
209
+ OperatorChoice(
210
+ parent_a=pick(),
211
+ parent_b=pick(),
212
+ mask=tuple(bool(rng.getrandbits(1)) for _ in range(n_loci)),
213
+ )
214
+ )
215
+ return out
216
+
217
+ return choose
218
+
219
+
220
+ def run_genetic_loop(
221
+ *,
222
+ problem: Any,
223
+ config: GeneticConfig,
224
+ chooser: Optional[OperatorChooser] = None,
225
+ log: Callable[[str], None] = lambda _m: None,
226
+ ) -> SearchResult:
227
+ """Evolve a population under *problem*, spending at most the budget."""
228
+
229
+ from agent_evolve.session.loop import _build_search_result, _default_candidate_key
230
+
231
+ if config.generator is not None and config.portfolio is not None:
232
+ raise ValueError(
233
+ "generator and portfolio both construct the generation's "
234
+ "candidates: the generator draws the pool, the portfolio "
235
+ "recombines parents into it. Run one or the other."
236
+ )
237
+
238
+ specs = list(problem.objectives)
239
+ candidate_model = getattr(problem, "candidate_model", None)
240
+ rng = random.Random(config.seed)
241
+ cache = config.evaluation_cache
242
+ if config.evaluation_budget is not None:
243
+ cache.budget = config.evaluation_budget
244
+
245
+ seeds = [dict(c) for c in (config.seeds or tuple(problem.seeds()))]
246
+ if not seeds:
247
+ raise ValueError(
248
+ "the genetic loop needs at least one seed to know the shape of a "
249
+ "candidate. Give Problem.seeds() one configuration, or use "
250
+ "proposer='llm' with the authoring loop."
251
+ )
252
+
253
+ def spent() -> int:
254
+ return int(getattr(cache, "misses", 0))
255
+
256
+ def budget_left() -> int:
257
+ if config.evaluation_budget is None:
258
+ return 1 << 30
259
+ return max(0, config.evaluation_budget - spent())
260
+
261
+ from agent_evolve.policies.search_state import SearchState
262
+
263
+ # --- the cheaper evaluation fidelity, if the problem has one -----------
264
+ # The source holds the problem's `evaluate_proxy` bound method and nothing
265
+ # else -- no problem, no cache, no budget -- so a proxy evaluation has no
266
+ # route to a charge. It is attached to the SCREEN, which is the only
267
+ # consumer that can use cheap evidence without putting it in the archive.
268
+ proxy_source: Any = None
269
+ if config.proxy_fidelity != "off" and config.screening is not None:
270
+ from agent_evolve.session.fidelity import ProxySource
271
+
272
+ proxy_source = ProxySource.for_problem(
273
+ problem, ceiling=config.proxy_ceiling)
274
+ if proxy_source is not None:
275
+ config.screening.attach_proxy(
276
+ proxy_source, mode=config.proxy_fidelity)
277
+
278
+ all_valid: List[Any] = []
279
+ all_meta: List[tuple] = []
280
+ history: List[Dict[str, Any]] = []
281
+ # The state accumulates across generations and is handed to the chooser
282
+ # each time. The unguided chooser ignores it, which is what makes the two
283
+ # arms differ in exactly one thing.
284
+ state = config.state if config.state is not None else SearchState()
285
+ state.history = history
286
+
287
+ def measure(configs: List[Config], gen: int) -> List[Any]:
288
+ room = budget_left()
289
+ if room <= 0:
290
+ return []
291
+ valid, failed, _ordered = evaluate_batch(
292
+ problem, configs[:room], specs, cache=cache
293
+ )
294
+ all_valid.extend(valid)
295
+ for result in list(valid) + list(failed):
296
+ all_meta.append((result, {"generation": gen}))
297
+ # Every measured candidate feeds the per-locus table, including ones the
298
+ # population does not keep: what was tried and rejected is exactly the
299
+ # evidence that a locus is saturated.
300
+ state.evaluated.extend((r.configuration, dict(r.objectives)) for r in valid)
301
+ # --- actionable side information ----------------------------------
302
+ # Failures verbatim: what the validator or evaluator said is exactly
303
+ # the diagnostic the score-only condition throws away.
304
+ for result in failed:
305
+ message = getattr(result, "error_message", None)
306
+ if message:
307
+ state.side_information.append(f"rejected: {message}")
308
+ # Optional problem hook -- an opt-in sixth obligation, never required.
309
+ hook = getattr(problem, "side_information", None)
310
+ if callable(hook):
311
+ for result in valid:
312
+ try:
313
+ text = hook(result.configuration, dict(result.objectives))
314
+ except Exception as exc: # a hook must not kill a run,
315
+ state.side_information.append( # but must not vanish either
316
+ f"side_information hook raised {type(exc).__name__}: {exc}"
317
+ )
318
+ break
319
+ if text:
320
+ state.side_information.append(str(text))
321
+ del state.side_information[:-64] # bounded, newest kept
322
+ return valid
323
+
324
+ def remember_measured(results: Sequence[Any],
325
+ surviving: Optional[Set[str]] = None) -> None:
326
+ """Report charged measurements to the generator as EVIDENCE.
327
+
328
+ Every charge the run makes, whoever proposed it. A generator that
329
+ reasons over measurements is reasoning about the SPACE, and the space
330
+ does not care which component produced the point: the initial
331
+ population and the structure screen are measurements this run paid
332
+ for, and withholding them leaves the channel blind until the loop has
333
+ spent two generations reproducing evidence it already had. That was
334
+ W11, measured: the locus prior could not be authored before a median
335
+ charge of 40 on a venue whose dominant knob is legible by charge 19.
336
+
337
+ Attribution is the separate call: a generator is credited only with
338
+ the children it drew, so its survival counters -- and the revision and
339
+ unwind rules that read them -- are untouched by what it is shown.
340
+ """
341
+
342
+ generator = config.generator
343
+ note = getattr(generator, "note_measured", None)
344
+ if generator is None or not results or note is None:
345
+ return
346
+ kept = surviving or set()
347
+ for result in results:
348
+ note(result.configuration,
349
+ objectives=dict(result.objectives),
350
+ survived=_default_candidate_key(result.configuration) in kept)
351
+
352
+ # --- initial population: the seeds, then SCHEMA-UNIFORM draws -----------
353
+ # Not mutants of the seed. A population of near-copies of one
354
+ # configuration is an anchored cloud around it; measured on a third-party
355
+ # optimizer, correcting exactly this anchor moved its result from +0.095
356
+ # (loses badly to uniform) to +0.0066 (parity), and the standard seed on
357
+ # log2 scores worse than a typical uniform draw.
358
+ n_loci = len(loci_of(seeds[0]))
359
+
360
+ # --- optional structure phase: buy a model of the landscape first -------
361
+ # Operator choice cannot escape the distribution it samples from, so before
362
+ # building a population the loop can spend a few evaluations on a CROSSED
363
+ # screen, read which locus values the evidence refutes, and narrow the
364
+ # domains every later draw sees. The screen costs real budget and the prior
365
+ # can be wrong, so both are recorded and the bet is checked below.
366
+ restriction = config.restriction
367
+ screen_front: List[Mapping[str, float]] = []
368
+ structure_record: Dict[str, Any] = {}
369
+ prior_proposer_used: Any = None
370
+ if config.structure_budget > 0 and restriction is None:
371
+ from agent_evolve.policies.structure import (
372
+ attribute, crossed_screen, statistical_prior)
373
+
374
+ screened = crossed_screen(seeds[0], candidate_model,
375
+ size=config.structure_budget, rng=rng,
376
+ pool_by_field=config.structure_pooled)
377
+ screen_valid = measure(screened, 0)
378
+ # Charged, therefore evidence. The screen's points never enter a
379
+ # population, so none of them is marked survived -- an absent verdict
380
+ # reported as absent rather than invented.
381
+ remember_measured(screen_valid)
382
+ if screen_valid:
383
+ attr = attribute(
384
+ [(r.configuration, dict(r.objectives)) for r in screen_valid],
385
+ specs, candidate_model,
386
+ pool_by_field=config.structure_pooled)
387
+ propose = config.prior_proposer or statistical_prior
388
+ prior_proposer_used = propose
389
+ try:
390
+ restriction = propose(attr, candidate_model)
391
+ except Exception as exc: # a proposer must not kill a run
392
+ structure_record["proposer_error"] = f"{type(exc).__name__}: {exc}"
393
+ restriction = None
394
+ # Keep what the screen itself achieved, as objective vectors: the
395
+ # unwind test below asks whether the restricted search can still
396
+ # match it, and domination is the only comparison that means
397
+ # anything across several objectives.
398
+ # Keep the screen's non-dominated points that the prior EXCLUDES.
399
+ # Those are exactly the claims the prior makes: it asserts this
400
+ # region is not worth sampling. If the restricted search cannot
401
+ # beat even one of them, the assertion is unsupported and the bet
402
+ # comes off. Pooled rank-0 would be the weaker test and a useless
403
+ # one -- a restricted region always holds points that are
404
+ # non-dominated along some other objective, so it could never fire.
405
+ screen_ranks = domination_rank(
406
+ [dict(r.objectives) for r in screen_valid], specs)
407
+ allowed = dict(getattr(restriction, "allowed", {}) or {})
408
+
409
+ def _excluded(cfg: Mapping[str, Any]) -> bool:
410
+ return any(cfg.get(k) not in tuple(vals)
411
+ for k, vals in allowed.items())
412
+
413
+ screen_front = [dict(r.objectives)
414
+ for r, rank in zip(screen_valid, screen_ranks)
415
+ if rank == 0 and _excluded(r.configuration)]
416
+ structure_record.update(
417
+ screened=len(screened), evaluated=len(screen_valid),
418
+ allowed=dict(getattr(restriction, "allowed", {}) or {}),
419
+ misses=list(getattr(restriction, "misses", []) or []),
420
+ )
421
+ log(f"structure: screened {len(screen_valid)}, prior "
422
+ f"{structure_record.get('allowed') or 'none'}")
423
+
424
+ initial = list(seeds)
425
+ for proposal in config.initial_proposals:
426
+ if len(initial) < config.population_size:
427
+ initial.append(dict(proposal))
428
+ while len(initial) < config.population_size:
429
+ template = initial[rng.randrange(len(initial))]
430
+ initial.append(uniform_candidate(template, candidate_model, rng=rng,
431
+ restriction=restriction))
432
+ valid = measure(initial, 0)
433
+ if not valid:
434
+ raise RuntimeError(
435
+ "no seed evaluated successfully, so there is nothing to evolve from"
436
+ )
437
+
438
+ # The population carries OBJECTIVES, not ranks: a rank is only meaningful
439
+ # relative to the set it was computed in, so it must be recomputed whenever
440
+ # the set changes rather than carried forward from a previous generation.
441
+ def survive(
442
+ pool: List[tuple[Config, Mapping[str, float]]]
443
+ ) -> List[tuple[Config, Mapping[str, float]]]:
444
+ ranks = domination_rank([obj for _c, obj in pool], specs)
445
+ kept = truncation_survival(
446
+ [((c, o), r) for (c, o), r in zip(pool, ranks)],
447
+ keep=config.population_size,
448
+ key_of=lambda pair: _default_candidate_key(pair[0]),
449
+ )
450
+ return [pair for pair, _rank in kept]
451
+
452
+ population = survive([(r.configuration, dict(r.objectives)) for r in valid])
453
+ history.append({"gen": 0, "valid_count": len(valid), "pop": len(population)})
454
+ log(f"generation 0: {len(valid)} evaluated, population {len(population)}")
455
+ if config.generator is not None:
456
+ # What the run has already measured, so the novelty guard can tell a
457
+ # candidate that is new from one the generator is re-proposing.
458
+ config.generator.note_archive([r.configuration for r in valid])
459
+ # ... and what those measurements SAID. The initial population is the only
460
+ # evidence in existence when the first pool is drawn; a channel that cannot
461
+ # see it cannot speak until generation 2, which is the W11 defect.
462
+ remember_measured(valid,
463
+ {_default_candidate_key(c) for c, _o in population})
464
+
465
+ pick = chooser or random_chooser(rng, n_loci)
466
+ # Guidance picks the revision channel returned last generation. They are
467
+ # authored members, like the initial proposals, so they wait for a
468
+ # generation of their own rather than displacing offspring already built.
469
+ immigrants: List[Config] = []
470
+ # Pool extras draw from their own stream: the main stream must spend
471
+ # exactly the same draws whether or not screening is on, or "off" stops
472
+ # being byte-identical to the pre-screening seam.
473
+ rng_pool = random.Random(0 if config.seed is None else (config.seed ^ 0x5CEE11))
474
+
475
+ for gen in range(1, config.generations + 1):
476
+ if budget_left() <= 0:
477
+ log(f"budget exhausted after {spent()} evaluations; stopping at gen {gen}")
478
+ break
479
+ want = min(config.offspring_per_generation, budget_left())
480
+ # The chooser sees ranks, not raw objectives: it decides which parents to
481
+ # combine, and a rank is the comparable form of "how good is this one".
482
+ ranks = domination_rank([obj for _c, obj in population], specs)
483
+ ranked = [(c, r) for (c, _o), r in zip(population, ranks)]
484
+ filled = 0
485
+ choices: Sequence[OperatorChoice] = ()
486
+ # An authored generator draws the whole generation, so there is no
487
+ # parent choice to make and the chooser is not consulted -- calling it
488
+ # and discarding the answer would spend a model call on nothing.
489
+ if config.generator is None:
490
+ choices = list(pick(ranked, want, state))[:want]
491
+ # A chooser that returns too few must not silently shrink the
492
+ # generation: the arm would then spend less budget than the control
493
+ # it is compared against. Top up at random and record how many, so
494
+ # the shortfall shows up in the result instead of in the conclusion.
495
+ if len(choices) < want:
496
+ filled = want - len(choices)
497
+ choices = list(choices) + list(
498
+ random_chooser(rng, n_loci)(ranked, filled, None)
499
+ )
500
+ def build_kid(choice: OperatorChoice, r: random.Random) -> Config:
501
+ a = population[choice.parent_a % len(population)][0]
502
+ b = population[choice.parent_b % len(population)][0]
503
+ mask = choice.mask
504
+ # The mask is fitted to the parent it is about to be applied to,
505
+ # NOT to `n_loci`. A locus count is a property of a candidate, not
506
+ # of a problem: a field holding a sequence contributes one locus per
507
+ # element, so two candidates of the same problem legitimately have
508
+ # different genome lengths. `n_loci` is read once from seeds[0], and
509
+ # using it here made the shipped knapsack example -- whose seeds are
510
+ # a 1-item and a 3-item selection -- crash deterministically on the
511
+ # first generation, because a 1-bit mask met a 3-locus parent.
512
+ want_bits = len(loci_of(a))
513
+ if len(mask) != want_bits: # a chooser may be wrong; the
514
+ mask = mask[:want_bits] + tuple( # loop must not crash on it
515
+ bool(r.getrandbits(1)) for _ in range(want_bits - len(mask))
516
+ )
517
+ kid = crossover(a, b, mask=mask)
518
+ return mutate(kid, candidate_model, rate=config.mutation_rate,
519
+ restriction=restriction,
520
+ loci=choice.mutate_loci, rng=r)
521
+
522
+ kid_origins: Optional[List[str]] = None
523
+ # --- mass generation: the model wrote the sampler, not the samples --
524
+ # The pool is many times what the budget can afford, and costs the
525
+ # budget nothing: the generator is handed a template, the domains, and
526
+ # the archive -- never the problem and never the cache -- so the only
527
+ # candidates that can become evaluations are the `want` below.
528
+ pool_kids: Optional[List[Config]] = None
529
+ if config.generator is not None:
530
+ pool_kids = config.generator.propose(
531
+ template=seeds[0], candidate_model=candidate_model,
532
+ restriction=restriction,
533
+ archive=[c for c, _o in population],
534
+ want=want, rng=rng_pool,
535
+ seed=(config.seed or 0) * 1000 + gen)
536
+ kids = [dict(kid) for kid in pool_kids[:want]]
537
+ elif config.portfolio is not None:
538
+ pairs = [
539
+ (population[choice.parent_a % len(population)][0],
540
+ population[choice.parent_b % len(population)][0])
541
+ for choice in choices
542
+ ]
543
+ kids, kid_origins = config.portfolio.construct_generation(
544
+ pairs, candidate_model, restriction, rng, generation=gen)
545
+ else:
546
+ kids = [build_kid(choice, rng) for choice in choices]
547
+
548
+ # --- virtual pre-screening: build more than we can afford, pay for
549
+ # the promising. The surrogate is re-validated on today's data before
550
+ # it may order anything (the gate is the arbitration), a floor of the
551
+ # chooser's own picks is always measured unscreened (the screen must
552
+ # never own the whole generation), and only measure() below touches
553
+ # the budget -- the screen has no route to it by construction.
554
+ screen_note: Optional[Dict[str, Any]] = None
555
+ if config.screening is not None and kids:
556
+ # Cheap-fidelity evidence FIRST, so the gate this generation sees
557
+ # the rows the campaign could not afford. It buys evidence about
558
+ # THIS generation's candidates, which is the distribution the
559
+ # screen is about to rank, and it charges nothing.
560
+ if proxy_source is not None:
561
+ config.screening.prime(
562
+ kids if pool_kids is None else pool_kids,
563
+ [proxy_source.key(c) for c, _o in state.evaluated])
564
+ active = config.screening.refresh(
565
+ list(state.evaluated), specs,
566
+ seed=(config.seed or 0) + gen)
567
+ screen_note = {"pool": len(kids), "held_out": len(kids),
568
+ "advanced": 0, "active": bool(active)}
569
+ if active:
570
+ if pool_kids is None:
571
+ extra_n = (config.screening.pool_factor - 1) * len(kids)
572
+ extra_choices = random_chooser(rng_pool, n_loci)(
573
+ ranked, extra_n, None)
574
+ pool_kids = kids + [build_kid(c, rng_pool)
575
+ for c in extra_choices]
576
+ pool_origins = (None if kid_origins is None else
577
+ list(kid_origins)
578
+ + ["pool"] * (len(pool_kids) - len(kids)))
579
+ report = config.screening.screen(
580
+ pool_kids, [obj for _c, obj in population], specs)
581
+ if report is not None:
582
+ # The floor is the screen's own, not a constant: a screen
583
+ # certified on some of the objectives is biased against
584
+ # the ones it cannot see, so it reserves more of the
585
+ # generation for the chooser's unscreened picks.
586
+ floor_n = min(len(kids), max(1, math.ceil(
587
+ config.screening.exploration_floor_for(report) * want)))
588
+ keep = list(range(floor_n))
589
+ for index in report.order:
590
+ if len(keep) >= want:
591
+ break
592
+ if index >= floor_n:
593
+ keep.append(index)
594
+ kids = [pool_kids[index] for index in keep[:want]]
595
+ if pool_origins is not None:
596
+ kid_origins = [pool_origins[index]
597
+ for index in keep[:want]]
598
+ screen_note = {
599
+ "pool": len(pool_kids), "held_out": floor_n,
600
+ "advanced": len(kids) - floor_n, "active": True,
601
+ "surrogate": report.surrogate_name,
602
+ # Which objectives this order was actually computed
603
+ # over. A generation that screened on a subset must
604
+ # not be readable as one that screened on the whole
605
+ # problem, so the scope travels in the record beside
606
+ # the count of what it advanced.
607
+ "objectives": list(report.screened_objectives),
608
+ "objectives_declared": list(report.declared_objectives),
609
+ "partial": bool(report.partial),
610
+ }
611
+
612
+ # --- immigrants: authored members, ahead of the offspring -----------
613
+ # They bypass the screen exactly as the initial proposals do: the
614
+ # screen orders what the loop CONSTRUCTED, and a member the model
615
+ # authored from the run's measurements is a guidance pick whose bet is
616
+ # settled by the evaluator, not by a surrogate.
617
+ injected = 0
618
+ if immigrants:
619
+ kids = [dict(member) for member in immigrants] + list(kids)
620
+ kids = kids[:want]
621
+ injected = min(len(immigrants), len(kids))
622
+ immigrants = []
623
+
624
+ valid = measure(kids, gen)
625
+ # Survivors compete against this generation's offspring on equal terms;
626
+ # both carry their own objectives, so the rank is recomputed over the
627
+ # merged set rather than inherited from the set it was measured in.
628
+ pool: List[tuple[Config, Mapping[str, float]]] = list(population)
629
+ pool.extend((r.configuration, dict(r.objectives)) for r in valid)
630
+ population = survive(pool)
631
+
632
+ # --- survival credit: a mechanism is what its children survive ------
633
+ if config.generator is not None and valid:
634
+ surviving = {_default_candidate_key(c) for c, _o in population}
635
+ for result in valid:
636
+ config.generator.record_measured(
637
+ result.configuration,
638
+ survived=(_default_candidate_key(result.configuration)
639
+ in surviving),
640
+ objectives=dict(result.objectives))
641
+
642
+ if config.portfolio is not None and kid_origins is not None and valid:
643
+ surviving = {_default_candidate_key(c) for c, _o in population}
644
+ origin_by_key: Dict[str, str] = {}
645
+ for kid, origin in zip(kids, kid_origins):
646
+ origin_by_key.setdefault(_default_candidate_key(kid), origin)
647
+ for result in valid:
648
+ key = _default_candidate_key(result.configuration)
649
+ origin = origin_by_key.get(key)
650
+ if origin and origin != "pool":
651
+ config.portfolio.record_measured(
652
+ origin, survived=key in surviving)
653
+ for name in config.portfolio.review():
654
+ log(f"generation {gen}: operator arm {name!r} retired -- no "
655
+ "survivors in at least 4 measured children while the "
656
+ "classical arm has some")
657
+
658
+ # --- the prior is a bet, and a bet must be checkable -----------------
659
+ # A restriction that removed the good region cannot recover on its own,
660
+ # so once the restricted search has had a generation to show something,
661
+ # ask whether it can still match what the screen already found. If not,
662
+ # drop the restriction for the remainder and say so. Unwinding a prior
663
+ # that is merely unlucky costs less than holding one that is wrong.
664
+ if restriction is not None and screen_front and not structure_record.get("unwound"):
665
+ beat_something = any(
666
+ dominates(obj, excluded, specs)
667
+ for _c, obj in population
668
+ for excluded in screen_front)
669
+ if not beat_something:
670
+ restriction = None
671
+ structure_record["unwound"] = gen
672
+ log(f"generation {gen}: the prior stopped paying; restriction "
673
+ "dropped for the remainder")
674
+
675
+ # --- the prior is also REVISABLE ------------------------------------
676
+ # The unwind above can only drop a prior; it cannot correct one. A
677
+ # prior authored before the run measured anything is right about the
678
+ # rows it was authored from and says nothing about the rows that came
679
+ # after, so the channel that reads those rows runs here, after
680
+ # survival, on its own declared cadence.
681
+ reguide_note: Optional[Dict[str, Any]] = None
682
+ if config.reguidance is not None:
683
+ outcome = config.reguidance.maybe_revise(
684
+ rows=list(state.evaluated), population=list(population),
685
+ specs=specs, restriction=restriction, charges=spent(), gen=gen)
686
+ if outcome.restriction is not None:
687
+ restriction = outcome.restriction
688
+ if outcome.immigrants:
689
+ immigrants = [dict(member) for member in outcome.immigrants]
690
+ reguide_note = outcome.note
691
+ if reguide_note is not None:
692
+ log(f"generation {gen}: the sampling prior was revisited at "
693
+ f"{spent()} charged evaluations")
694
+
695
+ entry = {"gen": gen, "valid_count": len(valid),
696
+ "pop": len(population),
697
+ "choices_filled_at_random": filled}
698
+ if reguide_note is not None:
699
+ entry["reguide"] = reguide_note
700
+ if injected:
701
+ entry["reguide_immigrants"] = injected
702
+ if screen_note is not None:
703
+ entry["screen"] = screen_note
704
+ if config.generator is not None:
705
+ entry["generate"] = config.generator.note()
706
+ if config.portfolio is not None:
707
+ entry["portfolio"] = config.portfolio.summary()
708
+ history.append(entry)
709
+ if filled:
710
+ log(f"generation {gen}: the chooser supplied {want - filled} of "
711
+ f"{want} choices; {filled} were filled at random")
712
+ log(f"generation {gen}: {len(valid)} evaluated, {spent()} of "
713
+ f"{config.evaluation_budget} budget used")
714
+
715
+ if structure_record:
716
+ history.append({"structure": dict(structure_record)})
717
+ result = _build_search_result(
718
+ all_valid, all_meta, specs, history,
719
+ evaluations=spent(), candidate_key=_default_candidate_key,
720
+ )
721
+ # Telemetry is attached even when every mechanism was counter-free: an
722
+ # empty mechanism list on a random run is a measured zero, and a guided
723
+ # run's counters are the difference between "guidance did not help" and
724
+ # "guidance never arrived".
725
+ virtual = 0
726
+ if config.screening is not None:
727
+ virtual = int(getattr(
728
+ config.screening.telemetry, "virtual_evaluations", 0))
729
+ # Proxy evaluations are reported BESIDE the charged count, never inside
730
+ # it: `real_evaluations` is what the budget bought and what any
731
+ # evaluation-efficiency claim is denominated in.
732
+ proxy_evaluations = (0 if proxy_source is None
733
+ else int(proxy_source.ledger.evaluations))
734
+ return replace(result, telemetry=harvest_telemetry(
735
+ (pick, prior_proposer_used, config.screening,
736
+ getattr(config.screening, "author", None),
737
+ config.portfolio, getattr(config.portfolio, "author", None),
738
+ config.generator, getattr(config.generator, "author", None),
739
+ config.reguidance, proxy_source),
740
+ real_evaluations=spent(), virtual_evaluations=virtual,
741
+ proxy_evaluations=proxy_evaluations,
742
+ ))